回文树(也就是回文自动机)实际上是奇偶两棵树,每一个节点代表一个本质不同的回文子串(一棵树上的串长度全部是奇数,另一棵全部是偶数),原串中每一个本质不同的回文子串都在树上出现一次且仅一次。

一个节点的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 }
BZOJ2160

相关文章: