Codeforces 300(#181 Div2 only) D. Painting Square
問題
nxnの小正方形が並んだ正方形がある。大きな正方形の外側は黒い。
この正方形に対して以下の操作をk回行う。
- 一辺の長さが3以上の奇数の正方形であって、内部が全部白で、外側は黒いものを選ぶ
- 辺の長さをLとしたとき、内部の小正方形で(L + 1) / 2行目および(L + 1) / 2列目にある小正方形を全て黒に塗る
この操作を行った後の正方形の状態は何通りありうるか求めよ。
制約条件
n≦10^9
k≦1000
方針
正方形を"どのくらいの深さまで操作できるか"をdとする。
(dは、nが3以上の奇数である間n = (n + 1) / 2とできる回数)
すると、求める場合の数は深さがdの4分木であって、
各頂点は黒または白、ある頂点が黒のとき、その親の頂点も黒
であるような木のうち、黒の頂点の数がk個であるようなものの個数。
dp[depth][k]を、深さdepthで、黒い頂点がk通りあるときの個数とすると、
dp[i + 1][j + k + l + m + 1] = Σdp[i][j] * dp[i][k] * dp[i][l] * dp[i][m]
の漸化式が成り立つので、計算すればよい。
このままだとO(k^4)かかってしまうが、
dp[i][j] * dp[i][k]の部分をまとめてd[j + k]と計算しておけば、
dp[i + 1][j + k + 1] = Σd[j] * d[k]として求められる。
この計算は畳み込みなので、剰余環上のFFTを使うのが想定解だったらしいのだけど、
このO(k^2 logn)の解法で300msくらいで通ってしまうのでぜんぜんいみない…
k=50000くらいだったらFFT必須になりそうなのに。
ソースコード
const int mod = 7340033; ll dp[33][1024], d[1024]; int main(){ dp[0][0] = 1; rep(i, 31){ memset(d, 0, sizeof(d)); dp[i + 1][0] = 1; rep(j, 1001) rep(k, 1001 - j) d[j + k] += dp[i][j] * dp[i][k]; rep(j, 1001) d[j] %= mod; rep(j, 1001) rep(k, 1000 - j) dp[i + 1][j + k + 1] += (ll)d[j] * d[k]; rep(j, 1001) dp[i + 1][j] %= mod; } int q; scanf("%d", &q); while(q--){ int n, k; scanf("%d%d", &n, &k); int j = 0; while(n > 1 && n % 2) j++, n /= 2; printf("%d\n", dp[j][k]); } return 0; }