思路 首先把字符串变为 \(S[1]S[n]s[2]s[n-1] \dots\) 这样原来的一个合法的划分方案就变成了用k个长度为偶数的回文子串划分的方案, 然后直接DP,对i位置,可转移的位置就是它的几个回文
思路
首先把字符串变为\(S[1]S[n]s[2]s[n-1] \dots\)
这样原来的一个合法的划分方案就变成了用k个长度为偶数的回文子串划分的方案,
然后直接DP,对i位置,可转移的位置就是它的几个回文后缀,在PAM上跳fail即可
但是复杂度是假的,一旦串的每个字符都相同,就需要跳\(O(n)\)次fail,总复杂度变成了\(O(n^2)\)
所以有这样一个性质,对一个节点x,它的所有fail的len最多是log个等差数列,因为对于长度大于\(\frac{len}{2}\)的情况,由于回文树的性质,长度一定是一个等差数列,每次len/2,所以有log段
考虑直接对这log端的贡献转移,在当前的位置i,设之前三个位置为a1,a2,a3(a1>a2>a3),由于回文串的性质S[i-a1,i-d]=S[i-a2,i],S[i-a2,i-d]=S[i-a3,i],所以a2,a3在i-d处已经被统计过了,加上i-a1的贡献即可
复杂度\(O(n\log n)\)
代码
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MOD = 1e9+7; int trans[1000100][26],fail[1000100],Nodecnt,len[1000100],last,n,dp[1000100],pre[1000100],f[1000100],inc[1000100]; char mids[1000100],s[1000100]; int New_state(int _len){ len[Nodecnt]=_len; return Nodecnt++; } int get_fail(int p,int n){ while(mids[n-len[p]-1]!=mids[n]) p=fail[p]; return p; } void insert(int n){ int cur=get_fail(last,n); if(!trans[cur][mids[n]]){ int t=New_state(len[cur]+2); fail[t]=trans[get_fail(fail[cur],n)][mids[n]]; trans[cur][mids[n]]=t; inc[t]=len[t]-len[fail[t]]; if(inc[t]==inc[fail[t]]) pre[t]=pre[fail[t]]; else pre[t]=fail[t]; } last=trans[cur][mids[n]]; } int main(){ mids[0]=-1; New_state(0); fail[0]=1; New_state(-1); last=0; scanf("%s",s+1); n=strlen(s+1); // printf("n=%d\n",n); dp[0]=1; int inq=0; for(int i=1;i<=n/2;i++){ mids[++inq]=s[i]-'a'; insert(inq); for(int j=last;j;j=pre[j]){ f[j]=dp[inq-len[pre[j]]-inc[j]]; if(pre[j]!=fail[j]) f[j]=(f[j]+f[fail[j]])%MOD; if(!(inq&1)) dp[inq]=(dp[inq]+f[j])%MOD; } mids[++inq]=s[n-i+1]-'a'; insert(inq); for(int j=last;j;j=pre[j]){ f[j]=dp[inq-len[pre[j]]-inc[j]]; if(pre[j]!=fail[j]) f[j]=(f[j]+f[fail[j]])%MOD; if(!(inq&1)) dp[inq]=(dp[inq]+f[j])%MOD; } } // for(int i=1;i<=n;i++) // putchar(mids[i]+'a'); // putchar('\n'); printf("%d\n",dp[n]); return 0; }