Official

E - 工場の解体作業 / Factory Demolition Work Editorial by kyopro_friends


この問題は bitDP により解くことができます。

\(\mathrm{DP}[S]\) を「まだ壊されていない建物の集合が \(S\) であるとき、これらを解体するのにかかる費用の最小値」とします。

どの建物から解体するかを考えることで

\(\mathrm{DP}[S]=\min_{x\in S}(\mathrm{DP}[S\setminus\{x\}] + C_x + \sum_{y \not \in S}W_{y,x})\)

となります。右辺は \(O(N^2)\) で計算できることから、 \(O(2^N N^2)\) でこの問題を解くことができました。

実装例 (C++)

#include<bits/stdc++.h>
using namespace std;

int main(){
  int n;
  cin >> n;
  vector<int> c(n);
  for(int i=0; i<n; i++) cin >> c[i];
  vector<vector<int>> w(n, vector<int>(n));
  for(int i=0; i<n; i++)for(int j=0; j<n; j++) cin >> w[i][j];

  vector<long long> dp(1<<n, 1e18);
  dp[0] = 0;

  for(int s=1; s<1<<n; s++){
    for(int x=0; x<n; x++){
      if(!((s>>x)&1)){
        continue;
      }
      long long cost  = 0;
      for(int y=0; y<n; y++){
        if(!((s>>y)&1)){
          cost+=w[y][x];
        }
      }
      dp[s] = min(dp[s], dp[s^(1<<x)] + c[x] + cost);
    }
  }

  cout << dp.back() << endl;
}

実装例 (Python)

N = int(input())
C = list(map(int, input().split()))
W = [list(map(int, input().split())) for _ in range(N)]

dp = [10**18] * (1<<N)
dp[0] = 0

for s in range(1, 1<<N):
  for x in range(N):
    if not (s>>x)&1:
      continue
    dp[s] = min(dp[s], dp[s^(1<<x)] + C[x] + sum(W[y][x] for y in range(N) if not (s>>y)&1))

print(dp[-1])

おまけ

なお、\(f(S,x):=\sum_{y \not \in S}W_{y,x}\) を全ての \((S, x)\) に対して前計算しておくことで、DPの遷移式の \(O(2^N N)\) で計算できるようになります。
\(f(S,x)\) を定義通りに計算していると、\(O(2^NN^2)\) かかってしましますが、\(S\) の index 最小の要素を \(i\) として \(f(S.x)=f(S\setminus\{i\},x)+W_{i,x}\) と計算することで、\(f(S,x)\) は再帰的に 1 つあたり \(O(1)\)、全体で \(O(2^NN)\) で計算することができ、全体で O\((2^NN)\) でこの問題を解くことができます。

posted:
last update: