bitDPでの集めるDPと配るDPについて
こんなツイートが
bitDPのほうが集めるイメージ強くない?(なんか結局bitの表現に慣れれるかどうかみたいなあれな気がしてきた)
— てんぷら (@tempura_pp) June 20, 2018
このときは「わかる」ってなってたんですけど
いざ問題を解いてみるとそんなこと無いな,なんでだろう,みないな記事です
以下の問題のネタバレを含みます 気をつけてください
https://yukicoder.me/problems/no/357
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0263
考える問題1
https://yukicoder.me/problems/no/357
空白
集めるDP
で書くとこうなる
#include <bits/stdc++.h> using namespace std; using i64 = long long; int N,M; i64 score[14][14]; i64 dp[1 << 14]; int main(){ cin >> N >> M; for(int i = 0;i < M;i++){ i64 a,b,c; cin >> a >> b >> c; score[a][b] = c; } for(int s = 1;s < (1 << N);s++){ for(int p = 0;p < N;p++){ int t = s & ~(1 << p); if(t == s) continue; i64 res = 0; for(int i = 0;i < N;i++){ if(t & (1 << i)){ res += score[i][p]; } } dp[s] = max(dp[s],dp[t] + res); } } cout << dp[(1 << N) - 1] << endl; }
集めるbitDPは
int t = s & ~(1 << p);
としてやることでp番のアイテムを置く前の状態
を表現できる
このとき
if(t == s) continue;
とするのが重要で,置く前の状態
になっているかを確認する
配るDP
で書くとこうなる
#include <bits/stdc++.h> using namespace std; using i64 = long long; int N,M; i64 score[14][14]; i64 dp[1 << 14]; int main(){ cin >> N >> M; for(int i = 0;i < M;i++){ i64 a,b,c; cin >> a >> b >> c; score[a][b] = c; } for(int s = 0;s < (1 << N);s++){ for(int p = 0;p < N;p++){ int t = s | (1 << p); if(t == s) continue; i64 res = 0; for(int i = 0;i < N;i++){ if(s & (1 << i)){ res += score[i][p]; } } dp[t] = max(dp[t],dp[s] + res); } } cout << dp[(1 << N) - 1] << endl; }
配るbitDPは
int t = s | (1 << p);
としてやることでp番を置いたあとの状態
を表現できる
if(t == s) continue;
も同様である
ここはあんまり差が無い
まあなんとかなるかなぁみたいな
本題
http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0263
もち
遷移
i回目の点灯の前の状態をstateとします
するとi回目の点灯があったあとはstate | A[i]
という状態になります
その後、プレイヤーがjのパターンでボタンを押すと(state | A[i]) & ~B[i]
という状態になります
このとき押したボタンの数は(state | A[i]) & B[i]
のビットの立っている数です
つまり遷移は
dp[i + 1][(s | A[i]) & ~(B[j])] = max(dp[i + 1][(s | A[i]) & ~(B[j])] , dp[i][s] + __builtin_popcount((s | A[i]) & B[j]));
これは配るDPですね
集めるDPは?
遷移が思いつきにくい
なんで?
ゲームはすすめるイメージが強いのかなぁ...
でもしっかり配るDPでできたし、こっちのほうが素直
#include <bits/stdc++.h> using namespace std; using i64 = long long; #define rep(i,s,e) for(int (i) = (s);(i) <= (e);(i)++) int N; int C; vector<i64> A,B; int dp[31][1 << 16]; int solve(){ rep(i,0,30){ rep(j,0,(1 << 16) - 1) dp[i][j] = -1e9; } dp[0][0] = 0; cin >> N >> C; if(N == 0 && C == 0){ return 0; } A.assign(N,0); B.assign(C,0); rep(i,0,N - 1){ rep(j,0,15){ int a; cin >> a; A[i] += a * (1LL << j); } } rep(i,0,C - 1){ rep(j,0,15){ int a; cin >> a; B[i] += a * (1LL << j); } } rep(i,0,N - 1){ rep(s,0,(1 << 16) - 1){ rep(j,0,C - 1){ auto & to = dp[i + 1][(s | A[i]) & ~(B[j])]; to = max(to , dp[i][s] + __builtin_popcount((s | A[i]) & B[j])); } } } int ans = 0; rep(s,0,(1 << 16) - 1){ ans = max(ans,dp[N][s]); } cout << ans << endl; return 1; } int main(){ while(solve()); }
まだ議論の余地がアリそうですね...(気分っていうのもあるかもしれない)