TCO2012 Round2B Hard SequenceTransmission
問題
b + a * i(i = 1, 2, .., n)で表される数列を、二進数で表現し、
初項から隙間をあけずに全部並べて書いた文字列をsとする。
この文字列に含まれる、"01"と、"10"の個数を求めよ。
制約条件
1≦a≦40000
1≦b≦10^18
1≦n≦10^12
方針
桁DP.
a + b ≦ x ≦ a * n + b
なるxののうち、aで割った余りがb % aに等しいもの数に含まれる、
"01" "10"の個数が簡単に求められればよい。
これは、簡単な桁DPによりできる。
lim未満の条件を満たす数をf(lim)とすれば、
求める個数はf(a * n + b + 1) - f(a + b)なので、f(lim)を求める。
dp[pos][mod][nz][prev][s]を、
pos桁目まで考えて、今のところの余りがmodで、
nzは0でない数が出たか、直前の数がprevで、sはlim未満であるか
の状態における、01 10の出現個数とする。
これを更新していくDPをすればよい。
ただし、その状態に至る場合の数が何通りかも覚えておく必要があるので、
way[pos][mod][nz][prev][s]をその状態に至る場合の数として、同時に更新する。
このDPでは、末尾の0によりできる01をカウントしていないので、
それは別個に数える。
末尾の0は、最後の項を除き、必ず01を作るので、末尾の0の数から求めればよい。
ソースコード
ll lo, hi, dp[2][40000][2][2][2], way[2][40000][2][2][2], a, b; char A[100], B[100]; ll calc(char *d, int n){ memset(dp, 0, sizeof(dp)); memset(way, 0, sizeof(way)); way[0][0][0][0][0] = 1; int cur = 0, next = 1; rep(i, n){ memset(dp[next], 0, sizeof(dp[next])); memset(way[next], 0, sizeof(way[next])); rep(mod, a) rep(nz, 2) rep(prev, 2) rep(s, 2) if(way[cur][mod][nz][prev][s]){ rep(j, 2){ int nmod = (mod * 2 + j) % a; int nnz = nz || j; int ns = s || d[i] > j; if(!ns && j > d[i]) continue; way[next][nmod][nnz][j][ns] += way[cur][mod][nz][prev][s]; dp[next][nmod][nnz][j][ns] += dp[cur][mod][nz][prev][s]; if(nz && prev != j) dp[next][nmod][nnz][j][ns] += way[cur][mod][nz][prev][s]; } } swap(cur, next); } return dp[cur][b % a][1][0][1] + dp[cur][b % a][1][1][1]; } class SequenceTransmission { public: long long signalChanges(long long a, long long b, long long N) { lo = a + b; hi = b + a * N + 1; ::a = a; ::b = b; memset(A, 0, sizeof(A)); int n, m; for(m = 0; hi; hi /= 2) B[m++] = hi % 2; for(n = m; lo; lo /= 2) A[--n] = lo % 2; reverse(B, B + m); ll ans = calc(B, m) - calc(A, m); if(a % 2 == 0) ans += (N - 1) * (b % 2 == 0); else ans += (N - 1 + ((a + b) % 2 == 0)) / 2; return ans; } };