回文树(也就是回文自动机)实际上是奇偶两棵树,每一个节点代表一个本质不同的回文子串(一棵树上的串长度全部是奇数,另一棵全部是偶数),原串中每一个本质不同的回文子串都在树上出现一次且仅一次。
一个节点的fail指针指向它的最长回文后缀(不包括自身,所有空fail均连向1)。归纳容易证明,当在原串末尾新增一个字符时,回文树上至多会新增一个节点,这也证明了一个串本质不同的回文子串个数不会超过n。
建树时采用增量构造法,当考虑新字符s[i]时,先找到以s[i-1]为结尾的节点p,并不断跳fail。若代表新增回文子串的节点已存在则直接结束,否则通过fail[p]不断跳fail找到新节点的fail。
0,1号节点均不代表串,常数大于manacher。初始化fail[0]=fail[1]=1,len[1]=-1,tot=1,last=0。
[BZOJ2160]拉拉队排练
建立后缀树后树上DP求出每种回文子串的出现次数即可。
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 typedef long long ll; 5 using namespace std; 6 7 const int N=1200010,mod=19930726; 8 char s[N]; 9 ll K,sm; 10 int n,ans=1,lst,nd=1,len[N],fail[N],son[N][27],sz[N]; 11 struct P{ int l,c; }c[N]; 12 bool operator <(const P &a,const P &b){ return a.l>b.l; } 13 14 int ksm(int a,int b){ 15 int s=1; 16 for (; b; a=1ll*a*a%mod,b>>=1) 17 if (b & 1) s=1ll*s*a%mod; 18 return s; 19 } 20 21 void ext(int c,int n,char s[]){ 22 int p=lst; 23 while (s[n-len[p]-1]!=s[n]) p=fail[p]; 24 if (!son[p][c]){ 25 int np=++nd,q=fail[p]; 26 while (s[n-len[q]-1]!=s[n]) q=fail[q]; 27 len[np]=len[p]+2; fail[np]=son[q][c]; son[p][c]=np; 28 } 29 lst=son[p][c]; sz[lst]++; 30 } 31 32 int main(){ 33 freopen("bzoj2160.in","r",stdin); 34 freopen("bzoj2160.out","w",stdout); 35 scanf("%d%lld%s",&n,&K,s+1); 36 len[1]=-1; fail[1]=fail[0]=1; 37 rep(i,1,n) ext(s[i]-'a',i,s); 38 for (int i=nd; i; i--) sz[fail[i]]+=sz[i]; 39 rep(i,2,nd) c[i-1]=(P){len[i],sz[i]}; 40 sort(c+1,c+nd); 41 rep(i,1,nd-1){ 42 if (!(c[i].l&1)) continue; 43 ll t=min(K,(ll)c[i].c); ans=1ll*ans*ksm(c[i].l,t)%mod; K-=t; 44 if (!K) break; 45 } 46 printf("%d\n",K?-1:ans); 47 return 0; 48 }