最小回文划分(回文树)

题意

将字符串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]即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值