Code Festival 2017 予選C D - Yet Another Palindrome Partitioning
面白い問題!
問題
英小文字のみからなる文字列sが与えられる
これをいくつかの空でない部分文字列s[1], s[2], ..., s[n]へ分割したい
更に任意のs[i]について、「s[i]の文字列を並び替えて回文が得られる」という条件も満たしたい
条件が成り立つようにsを分割するとき、分割数nの最小値を求めよ
1 <= |s| <= 2*10^5
解法
まず各文字を2進数表記にしたものの配列を考える
(具体的には文字cは1<<(c-'a')にする)
更にこれのxorでの累積和をとる
そうすることで出来る配列xのi番目は、「s[0,i]における各アルファベットの登場回数の偶奇(2で割った余り)」という状態を表す
つまりこれで立っているビットが1個以下ならその範囲は回文にできることになる
これを利用すればs[l,r]が回文にできるかはO(1)で求めることができる
これを利用したdpとしては
dp[i]: i文字目までの答え
dp[i] = min({dp[j] | s[j+1,i]が回文にできる}) + 1
というものがぱっと思いつく
けどこれだとO(|s|^2)になるから無理
そこでもう一つdp配列を用意する
dp2[b]: 今i文字目を見ているとして、x[j]=bとなるようなj(<i)のうちdp[j]が最小であるようなもののdp[j]の値 (存在しなければinf)
でx[i]のビットを1個(か0個)反転したもの全て(26個)をbとして試してmin(dp2[b]+1)でdp[i]を更新すれば、更新がO(1)で出来るようになる
int n, x, dp[200200], dp2[1<<26]; string s; signed main() { cin >> s; n = s.size(); reps(i,1,1<<26) dp2[i] = inf; rep(i,n) { x ^= 1<<(s[i]-'a'); int t = dp2[x]; rep(k,26) chmin(t, dp2[x^(1<<k)]); chmin(dp2[x], t+1); dp[i] = t+1; } cout << dp[n-1] << endl; }