DP?メモリ削減テクニック
今やっていた、SRM 430 DIV1 Medium TwinTowns。
このrng_58さんの解答を参考に書いた下のコード。
typedef long long ll; typedef vector<int> vi; typedef pair<int,int> pii; typedef vector<pii> vpii; const int INF = 0x3f3f3f3f; const ll INFL = 0x3f3f3f3f3f3f3f3fLL; #define X(m, i) (((m)>>((i)*2))&3) int dp[1<<20]; struct TwinTowns { vector <int> optimalTwinTowns(vector <int> x, vector <int> y, int maxPartners, int minDistance) { int n = x.size(); mset(dp, INF); dp[0] = 0; rep(i, n) rep(j, n) if(i < j) { int d = abs(x[i] - x[j]) + abs(y[i] - y[j]); if(d >= minDistance) { //※1 for(int mask = (1<<(n*2))-1; mask >= 0; mask --) { int d1 = X(mask, i), d2 = X(mask, j); if(d1 < maxPartners && d2 < maxPartners) { int mask2 = mask + (1<<(i*2)) + (1<<(j*2)); dp[mask2] = min(dp[mask2], dp[mask] + d); } } } } pii p = pii(0, 0); rep(mask, 1<<(n*2)) if(dp[mask] != INF){ cout << bitset<20>(mask) << ": " << dp[mask] << endl; int count = 0; rep(i, n) count += X(mask, i); p = max(p, pii(count / 2, -dp[mask])); } vi v; v.pb(p.first); v.pb(-p.second); return v; } };
この※1のfor、大きい方からmaskを回しているが、逆にすると答えが合わない。何故なら、そうすると同じペアで複数回作ってしまうからだ。
これは実際、そのままのDPではない。
本当は状態としてi,jのが必要であるが、順番を適切に上書きしていくことによって、メモリの削減ができる、という形のものである(?)。
これは二次元グリッドで、上一行だけ持って、上書きしていくようなものか?
実際、これをメモ化再帰で書こうとすると単純には上手くいかなくて、どうしても再現するならグローバルな変数が必要な気がする(実際にはよくわからない)
自分はDPをいつもメモ化再帰の形で書いているが、このテクニックの形、またそれを読むことにも慣れておく必要がありそうだ