公式

E - 倉庫の配置計画 / Warehouse Placement Plan 解説 by kyopro_friends


この問題は半分全列挙により解くことができます。

\(M\) 以下の条件は最後に確認すれば十分なため忘れてよいです。また、倉庫を作ったなら荷物を割り当てないのは損なので、区画 \(i\) に倉庫を作るならば荷物量 \(P_i\) を割り当てるとしてよいです。よって問題は「 \(\max_S \sum_{i\in S}P_i\) を求めよ」となります。

\(N\leq 20\) の場合

この場合は \(\mathrm{DP}[S]\) を「\(S\) に含まれない区画には倉庫を作らないときの答え」と定め、 \(S\) に含まれる index が最大の区画 \(f(S)\) に倉庫を建てるかどうか考えることで

\(\mathrm{DP}[S]=\max(\mathrm{DP}[S\setminus\{f(S)\}], \mathrm{DP}[S\setminus(f(S)と直接繋がっている区画の集合)] +P_{f(S)})\)

となります。

集合を適切に整数に対応させることで、集合に対する演算をbit演算により \(O(1)\) で行うことができるとしてよく、この DP テーブルの計算は \(S\) 1 つあたり \(O(1)\)、全体で \(O(2^N)\) でできます。

元の問題

\(N\) 個の区画を、要素数が等しくなるよう 2 つにわけ、\(X,Y\) とします。\(X\) のうち「倉庫を建てる可能性のある区画たち」を全探索することを考えます。このとき、\(X\) 側での荷物量は先程述べた通り \(O(2^\frac{N}{2})\) の前計算により \(O(1)\) で答えることができます。また、\(X\) 側で選んだ区画たちと直接繋がっている区画を除いたものが \(Y\) 側で倉庫を立てる可能性のある区画になります。これも同様に \(O(2^\frac{N}{2})\) の前計算により \(O(1)\) で答えることができます。
\(X\) 側で選んだ区画たちと直接繋がっている区画を除いたもの」を求めることも、適切な DP と bit 演算により全体で \(O(2^\frac{N}{2})\) でできるとしてよく、全体で \(O(2^\frac{N}{2})\) でこの問題を解くことができました。

この問題のように「前半半分の決め方を全探索し、後半半分の決め方は前半半分の決め方に基づいて高速に求める」という手法を俗に半分全列挙と呼ぶこともあります。

実装例 (C++)

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

int main(){
  int n, k;
  long long m;
  cin >> n >> m >> k;
  vector<long long>p(n);
  for(int i=0; i<n; i++) cin >> p[i];
  vector<long long> G(n);
  for(int i=0; i<k; i++){
    int u, v;
    cin >> u >> v;
    u--, v--;
    G[u] |= 1LL << v;
    G[v] |= 1LL << u;
  }
  for(int i=0; i<n; i++){
    G[i] |= 1LL << i;
  }

  int n1 = n / 2;
  int n2 = n - n1;

  // 前半
  int mask = (1<<n1) - 1;
  vector<long long>memo(1<<n1);
  for(int s=1; s<(1<<n1); s++){
    int i = 31 - __builtin_clz(s);
    memo[s] = max(memo[s ^ (1<<i)], memo[s & ~G[i] & mask] + p[i]);
  }

  long long ans = *max_element(memo.begin(), memo.end());

  // 後半
  vector<long long>memo2(1<<n2);
  vector<long long>ng(1<<n2);

  for(int s=1; s<(1<<n2); s++){
    int i = 31 - __builtin_clz(s);
    memo2[s] = max(memo2[s ^ (1<<i)], memo2[s & ~(G[i+n1]>>n1)] + p[i+n1]);
    ng[s] = ng[s^(1<<i)] | G[i+n1];
    ans = max(ans, memo2[s] + memo[~ng[s] & mask]);
  }

  cout << min(m, ans) << endl;
}

実装例 (Python)

N, M, K = map(int, input().split())
P = list(map(int, input().split()))
G = [1<<i for i in range(N)]
for _ in range(K):
  u, v = map(int, input().split())
  u -= 1
  v -= 1
  G[u] |= 1<<v
  G[v] |= 1<<u

N1 = N // 2
N2 = N - N1

# 前半
mask = (1<<N1) - 1
memo = [0] * (1<<N1)
for s in range(1, 1<<N1):
  i = len(bin(s)) - 3
  memo[s] = max(memo[s ^ (1<<i)], memo[s & ~G[i] & mask] + P[i])

ans = max(memo)
# 後半
memo2 = [0] * (1<<N2)
ng = [0] * (1<<N2)
for s in range(1, 1<<N2):
  i = len(bin(s))-3
  memo2[s] = max(memo2[s ^ (1<<i)], memo2[s & ~(G[i+N1]>>N1)] + P[i+N1])
  ng[s] = ng[s^(1<<i)] | G[i+N1]
  ans = max(ans, memo2[s] + memo[~ng[s] & mask])

print(min(M, ans))

投稿日時:
最終更新: