Codeforces 932G Palindrome Partition dp+回文树

题意

给出一个长度为偶数的字符串S,要求把S分成k部分,其中k为任意偶数,设为a[1..k],且满足对于任意的i,有a[i]=a[k-i+1]。问划分的方案数。
n<=1000000

分析

刚了一个下午的题,感觉学到了很多新东西。
首先直接做显然不可做,考虑把S变成S[1]S[n]S[2]S[n-1]…S[n/2]S[n/2+1]的形式,不难发现对于原来划分方案中的a[i]和a[k-i+1],它们在新串中恰好连续且组成一个回文串。那么问题就变成了把新串划分成若干部分,使得每一部分都是偶回文串,问方案。
一开始的想法是把回文树建出来,然后设f[i]表示把前i个字符划分的方案数,然后枚举i的每一个回文后缀进行转移。
这样子的复杂度是O(回文串数量)的,显然会超时,考虑如何优化。
有一个小结论就是一个回文串S的一个后缀T是回文串当且仅当T是S的border。
有一个性质就是一个串的所有border可以划分成O(log)段等差数列。
证明的话,可以考虑对于所有长度大于n/2的border,它们显然组成一个等差数列,然后把长度减半,如此类推,那么等差数列的数量就只有O(log)个。
对于回文树上的每个节点p,我们可以维护以它作为等差数列末项时每一项的f值之和g[p],维护nxt[p]表示节点p下一个等差数列的末项。
怎么维护呢?设当前节点为末项的等差数列有b1,b2,b3,其中b1>b2>b3。那么有g[p]=f[i-b1]+f[i-b2]+f[i-b3]。
根据回文串的性质,不难发现S[i-b2,i-d]=S[i-b3,i],S[i-b1,i-d]=S[i-b2,i],那么在g[fail[p]]中就已经包含了f[i-b1]和f[i-b2],只要把f[i-b3]加上就好了。

代码

#include
#include
#include
#include
#include
using namespace std;

const int N=1000005;
const int MOD=1000000007;

int n,last,sz,len[N],fail[N],ch[N][26],id[N],f[N],diff[N],nxt[N],g[N];
char str[N],s[N];

int extend(int x,int n)
{
    int p=last;
    while (s[n-len[p]-1]!=s[n]) p=fail[p];
    if (!ch[p][x])
    {
        int now=++sz,k=fail[p];len[now]=len[p]+2;
        while (s[n-len[k]-1]!=s[n]) k=fail[k];
        fail[now]=ch[k][x];ch[p][x]=now;
        diff[now]=len[now]-len[fail[now]];
        if (diff[now]==diff[fail[now]]) nxt[now]=nxt[fail[now]];
        else nxt[now]=fail[now];
    }
    last=ch[p][x];return last;
}

int main()
{
    scanf("%s",str+1);
    n=strlen(str+1);
    int now=0;
    for (int i=1;i<=n;i+=2) s[i]=str[++now];
    now=n+1;
    for (int i=2;i<=n;i+=2) s[i]=str[--now];
    fail[0]=1;len[1]=-1;sz=1;
    for (int i=1;i<=n;i++) id[i]=extend(s[i]-'a',i);
    f[0]=1;
    for (int i=1;i<=n;i++)
        for (int p=id[i];p;p=nxt[p])
        {
            g[p]=f[i-(len[nxt[p]]+diff[p])];
            if (diff[p]==diff[fail[p]]) (g[p]+=g[fail[p]])%=MOD;
            if (!(i&1)) (f[i]+=g[p])%=MOD;
        }
    printf("%d",f[n]);
    return 0;
}

你可能感兴趣的:(动态规划,回文树)