TopCoder SRM 582 Div1 Medium ColorfulBuilding
問題
n本のビルがあって、i番目のビルは色がC[i], 高さがiである。
このビルを一列に並べたとき、前から見て色の境界がL-1個であるような並べ方の総数を求めよ。
制約条件
n≦2500
L≦2500
方針
editorialがリンクはないんだけど存在する。謎。
http://apps.topcoder.com/wiki/display/tc/SRM+582
O(N^3)の方針がまずこんな感じ。
ビルを大きい順に並べる。
dp[使ったビルの数][見えるビルの数][最前列のビルの色] := 場合の数
として、次に使うビルを先頭にもってくるか、先頭以外に入れるかで遷移。
コードにするとこんな感じ。
dp[0][1][C[0]] = 1; rep(i, n - 1) rep(j, L + 1) rep(k, C.size()){ dp[i + 1][j][k] += (i + 1) * dp[i][j][k]; if(k == C[i]){ dp[i + 1][j][C[i]] += dp[i][j][k]; } else{ dp[i + 1][j + 1][C[i]] += dp[i][j][k]; } }
で、この更新は
dp[i + 1][j][k]の書き換えと
dp[i + 1][j][ C[i] ]の書き換えの二つに分かれているのだけど、
前者はi+1倍するだけなので、わざわざrep(k, C.size())のループを毎回まわす必要がなさそう。
後者はsum[i] = Σ{dp[i][j] | j}を持っておけばループをまわさなくて済みそう。
で、DPを書き換えると以下のようになる。(ループを逆順にして配列は使いまわしている)
for(int j = L; j > 0 ; j--){ (sum[j] *= i) %= mod; //先頭以外に置く //先頭同じ色のやつなら先頭においてもj増えない、先頭違う色のやつの先頭に置いてj増やす (sum[j] += dp[j][C[i]] + sum[j - 1] - dp[j - 1][C[i]] + mod) %= mod; //先頭が同じやつだったらi+1通り (dp[j][C[i]] *= i + 1) %= mod; //先頭が違うやつから作る (dp[j][C[i]] += sum[j - 1] - dp[j - 1][C[i]] + mod) %= mod; }
で、これだと前のdpにおけるdp[i][j][違う色]を(i+1)倍していく処理が抜けている。
違う色を毎回ループで回したくなかったので、どうすればいいかというと、
違う色である間する掛け算を、色ごとにまとめておいて、
同じ色が出てきたらまとめておいた掛け算を掛ければよい。
ソースコード
const int mod = (int)1e9 + 9; ll dp[2600][2600]; ll sum[2600]; ll pending[2600]; class ColorfulBuilding { public: int count(vector <string> color1, vector <string> color2, int L) { memset(dp, 0, sizeof(dp)); memset(sum, 0, sizeof(sum)); rep(i, 2600) pending[i] = 1; string a, b; rep(i, color1.size()){ a += color1[i]; b += color2[i]; } int n = a.size(); vi v, C; rep(i, n) v.pb((int)a[i] * 256 + b[i]); sort(all(v)); v.erase(unique(all(v)), v.end()); rep(i, n) C.pb(lower_bound(all(v), (int)a[i] * 256 + b[i]) - v.begin()); reverse(all(C)); dp[1][C[0]] = 1; sum[1] = 1; for(int i = 1; i < n; i++){ rep(j, L + 1) (dp[j][C[i]] *= pending[C[i]]) %= mod; rep(j, C.size()) (pending[j] *= i) %= mod; pending[C[i]] = 1; for(int j = L; j > 0 ; j--){ (sum[j] *= i) %= mod; //先頭以外に置く //同じ色のやつなら先頭においてもj増えない、違う色のやつの先頭に置いてj増やす (sum[j] += dp[j][C[i]] + sum[j - 1] - dp[j - 1][C[i]] + mod) %= mod; //先頭が同じやつだったらi+1通り (dp[j][C[i]] *= i + 1) %= mod; //先頭が違うやつから作る (dp[j][C[i]] += sum[j - 1] - dp[j - 1][C[i]] + mod) %= mod; } } return sum[L]; } };