题意
将字符串s划分为k个子串s(1)+s(2)+……+s(k),其中每个子串都是回文串,求最小的k。
O(n^2)的解法
dp[i]=min{ dp[j]+1, j<i, s[j+1……i]是回文串 }
结合回文树,假设当前节点为x,则可以写成:
dp[i]=min{ dp[i-len[y]]+1, y是x的回文后缀 }
从x开始跳fail一直跳到偶根即可。
O(nlogn)的解法
有结论:
1.将回文串x的所有回文后缀按照长度递减的方式排列后,可以根据间隔的不同划分成至多log|x|个子集,每个子集中相邻像素的间隔是一样的。
在回文树上维护额外的信息diff和slink:
diff[x]=len[x]-len[fail[x]]
slink[x]表示x所在子集的下一个子集的最长回文后缀。当diff[x]!=diff[fail[x]]时,slink[x]=fail[x];当diff[x]==diff[fail[x]]时,slink[x]=slink[fail[x]]。
2.设x是在i位置的等差数列回文后缀子集中最长的那个,则在计算dp[i]时,所需要参考的所有dp[i-len[v]](slink[v]==slink[x]),除了dp[i-len[slink[x]]-diff[x]](x所在等差数列的最短回文后缀长度就是len[slink[x]]+diff[x]),在计算dp[i-diff[x]]时均已经被参考过。
我们维护一个g[x]:
while x!=偶根
如果diff[x]==diff[fail[x]], 则g[x]=min(g[fail[x]],dp[i-len[slink[x]]-diff[x]])
如果diff[x]!=diff[fail[x]], 则g[x]=dp[i-len[x]] // 其实这时len[x]=len[slink[x]]+diff[x]
dp[i]=min(dp[i],g[x]+1)
x=slink[x]
3.设u是以i为结尾的回文后缀,diff[u]==diff[fail[u]],那么fail[u]上一次出现的位置就是i-diff[u]。
这点保证了在g[x]参与更新dp值时具有正确的意义。
举例来说假如在j位置的状态为x,{u1,u2,u3}是x的一个回文后缀等差数列,间隔为diff[u],设fail[u3]=v,diff[u3]!=diff[v],即v不属于这个等差数列,由于len[u1]=len[v]+diff[u],那么在下标i挪动到j位置的过程中,有:
当i=j-2*diff[u]时,更新g[u3]=dp[j-2*diff[u]-len[u1]]
当i=j-diff[u]时,更新g[u2]=min(g[u3],dp[j-diff[u]-len[v]-diff[u]])
即g[u2]=min(dp[j-2*diff[u]-len[u1]],dp[j-diff[u]-len[u1]])
当i=j时,更新g[u1]=min(g[u2],dp[j-len[v]-diff[u]])
即g[u1]=min(dp[j-2*diff[u]-len[u1]],dp[j-diff[u]-len[u1]],dp[j-len[u1]])
即g[u1]=min(dp[j-len[u3]],dp[j-len[u2]],dp[j-len[u1]])
此时直接用g[u1]来更新dp[j]即可。