http://codeforces.com/contest/914/problem/C
あるxについて以下の変換を考える。
「g(x) := xを2ビット表記したときの1の個数の総和」
2進数表記された数Nが与えられる。
N以下の数でg(x)による変換をK回行うことで丁度1に出来る数は何個あるか(mod10^9+7)
前提知識
解法
http://codeforces.com/contest/914/submission/34415059
まず気付くべき性質がある
「2^1000以下の数はg(x)を1回通すと1000以下の数になる」
よって、N以下の数について変換を1回通した形、つまり、2進数にしたときの1の数だけを考えればいいことになる。
ここから、以下の桁DPを使う。
「dp[i][sm][isless] := 上位i桁が確定していて、1の数がcnt、未満かどうかがisless、のときの数の個数」
桁DPに慣れていないと難しいが、
if (N[i] == '0') { dp[i + 1][cnt][isless] += dp[i][cnt][isless]; // 末尾に0を置く。islessは変化しない if (isless) dp[i + 1][cnt + 1][isless] += dp[i][cnt][isless]; // 末尾に1を置く。これはここまでで未満になってないと置けない。 } else { dp[i + 1][cnt][1] += dp[i][cnt][isless]; // 末尾に0を置く。必ず未満になる。 dp[i + 1][cnt + 1][isless] += dp[i][cnt][isless]; // 末尾に1を置く。islessは変化しない }
この部分が遷移の大事な部分。
「f(x) := g(x)の変換によってxを1に変換する為の変換回数」とすると、全てのcntについてf(cnt)+1==Kとなる数の個数の総和を答える。
コーナーケースが結構ある。
K=0のときは1だけなので、答えは1
K=1のときは答えから1を引く必要がある。
これは1のcntは1であるが、これは操作を行っていないのでcntに1が多く含まれることになり、これをカウントしてしまうからである。
string N; int K; //--------------------------------------------------------------------------------------------------- int memo[1010]; int f(int x) { assert(0 < x); if (x == 1) return 0; if (memo[x]) return memo[x]; int y = x, cnt = 0; while (y) { if (y % 2 == 1) cnt++; y /= 2; } return memo[x] = f(cnt) + 1; } //--------------------------------------------------------------------------------------------------- // dp[i][cnt][isless] // 上位i桁が確定していて、1の数がcnt、未満かどうかがisless、のときの数の個数 mint dp[1010][1010][2]; void _main() { cin >> N >> K; if (K == 0) { cout << 1 << endl; return; } int n = N.length(); dp[0][0][0] = 1; rep(i, 0, n) rep(cnt, 0, n) rep(isless, 0, 2) { if (N[i] == '0') { dp[i + 1][cnt][isless] += dp[i][cnt][isless]; // put 0 if (isless) dp[i + 1][cnt + 1][isless] += dp[i][cnt][isless]; // put 1 } else { dp[i + 1][cnt][1] += dp[i][cnt][isless]; // put 0 dp[i + 1][cnt + 1][isless] += dp[i][cnt][isless]; // put 1 } } mint ans = 0; rep(cnt, 1, n + 1) rep(isless, 0, 2) if (f(cnt) + 1 == K) ans += dp[n][cnt][isless]; if (K == 1) ans -= 1; cout << ans << endl; }