后缀自动机总结

来源:互联网 发布:mac口红官网价格多少 编辑:程序博客网 时间:2024/06/05 19:35

后缀自动机总结

后缀自动机的构造和相关性质及复杂度证明可以看陈老师的ppt

时间复杂度据说可以用均摊分析证明是O(n)的

一开始看直接看陈老师的ppt确实有点难以理解,但是陈老师的ppt确实是讲的最正规的一个

一些定义:right集合:后缀自动机中节点代表的子串的右端点位置构成的集合

     mins/maxs:节点代表的串的最短长度和最长长度


现在开始进入正题:

spoj1811:后缀自动机入门题,对一个串建自动机,另一个在上面匹配,记录匹配长度最大值即可

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=500010;using namespace std;int n,m,last;char a[maxn],b[maxn];struct TSam{int ch[maxn][26],tot,dis[maxn],fa[maxn],root;void add(int pos){int x=a[pos]-'a',p=last,np=++tot;last=np,dis[np]=pos;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=++tot;dis[nq]=dis[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q],fa[np]=fa[q]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;//printf("nq%d\n",dis[nq]);}}//printf("%d\n",dis[tot]);}}T;int main(){scanf("%s%s",a+1,b+1),last=T.root=++T.tot;n=strlen(a+1),m=strlen(b+1);for (int i=1;i<=n;i++) T.add(i);int ans=0,len=0,p=T.root;for (int i=1;i<=m;i++){int x=b[i]-'a';if (T.ch[p][x]) len++,p=T.ch[p][x];//puts("cas1");else{while (p&&!T.ch[p][x]) p=T.fa[p];if (!p) p=T.root,len=0;else len=T.dis[p]+1,p=T.ch[p][x];//puts("cas2");}ans=max(ans,len);//printf("len=%d\n",len);}printf("%d\n",ans);return 0;}/*alsdfkjfjkdsal fdjskalajfkdsla */


spoj1812&&bzoj2946:对一个串建后缀自动机,其他串在上面匹配,因为是求所有串的公共子串,所以每个点记录每个串最长匹配长度的最小值,最后找到所有点中最长的一个即可

一个注意事项就是,当走到一个点时,还要更新它的parent树上的祖先的匹配长度

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=500010;using namespace std;int n,cnt,sum[maxn],tmp[maxn],ans[maxn],res;char s[maxn];struct Tsam{int dis[maxn],ch[maxn][26],fa[maxn],tot,last,root,maxs[maxn];void init(){last=tot=root=1;}int newnode(int d){return dis[++tot]=d,tot;}void add(int pos){int x=s[pos]-'a',np=newnode(pos),p=last;last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void build(){init();scanf("%s",s+1),n=strlen(s+1);for (int i=1;i<=n;i++) add(i);for (int i=1;i<=tot;i++) ans[i]=dis[i];}void Tsort(){for (int i=1;i<=tot;i++) sum[dis[i]]++;//printf("%d\n",dis[i]);for (int i=1;i<=n;i++) sum[i]+=sum[i-1];for (int i=1;i<=tot;i++) tmp[sum[dis[i]]--]=i;//for (int i=1;i<=tot;i++) printf("%d %d\n",tmp[i],dis[tmp[i]]);}void work(){memset(maxs,0,sizeof(maxs));int len=0,p=root;for (int i=1;i<=n;i++){int x=s[i]-'a';if (ch[p][x]) len++,p=ch[p][x];else{for (;p&&!ch[p][x];p=fa[p]);if (!p) p=root,len=0;else len=dis[p]+1,p=ch[p][x];}maxs[p]=max(maxs[p],len);}for (int i=tot;i;i--){int x=tmp[i];ans[x]=min(ans[x],maxs[x]);if (maxs[x]&&fa[x]) maxs[fa[x]]=dis[fa[x]];//printf("x=%d ans=%d\n",x,ans[x]);//要记得更新祖先,因为我们可能到这个点而没有经过祖先,如果这个点可以到达,那么祖先一定是可以到dis[fa[x]]的//类似AC自动机里的fail树,我们能到fail树的儿子所代表的串,就一定能走到fail树祖先所代表的串//这里的pre指针构成的树也是类似//fa[x]所带表的一些串的right集合比x的大,且包含right[x],maxs[fa[x]]也比x要短//所以fa[x]的串其实是x的串的子串,x能到,fa[x]当然也能到}}}T;int main(){/*//freopen("test.in","r",stdin);T.build(),T.Tsort();while (scanf("%s",s+1)!=EOF) n=strlen(s+1),T.work();*/scanf("%d",&cnt);T.build(),T.Tsort();for (int i=1;i<cnt;i++) scanf("%s",s+1),n=strlen(s+1),T.work();for (int i=1;i<=T.tot;i++) res=max(res,ans[i]);printf("%d\n",res);return 0;}

spoj8222:构建字符串的自动机,对于每个节点,right集合大小就是出现次数,maxs就是它代表的最长长度,那么我们用|right(x)|去更新f[maxs[x]]的值,最后从大到小用f[i]去更新f[i-1]的值即可

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=500010;using namespace std;int n,f[maxn],sum[maxn],tmp[maxn];char s[maxn];struct Tsam{int dis[maxn],ch[maxn][26],fa[maxn],r[maxn],tot,last,root;void init(){last=tot=root=1;}int newnode(int d){return dis[++tot]=d,tot;}void add(int pos){int x=s[pos]-'a',p=last,np=newnode(pos);last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q],fa[np]=fa[q]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void work(){init();for (int i=1;i<=n;i++) add(i);for (int i=1,p=root;i<=n;i++)p=ch[p][s[i]-'a'],r[p]=1;for (int i=1;i<=tot;i++) sum[dis[i]]++;for (int i=1;i<=n;i++) sum[i]+=sum[i-1];for (int i=1;i<=tot;i++) tmp[sum[dis[i]]--]=i;for (int i=tot;i;i--) r[fa[tmp[i]]]+=r[tmp[i]];for (int i=1;i<=tot;i++) f[dis[i]]=max(f[dis[i]],r[i]);for (int i=n;i;i--) f[i]=max(f[i],f[i+1]);for (int i=1;i<=n;i++) printf("%d\n",f[i]);}}T;int main(){scanf("%s",s+1),n=strlen(s+1);T.work();return 0;}

bzoj2882:用后缀自动机实现最小表示法。把串复制一遍,构建后缀自动机,每次选择最小的边转移即可

因为字符集很大,所以转移边用map来存即可


#include<map>#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>#define it map<int,int>::iteratorconst int maxn=1200010;using namespace std;int n,s[maxn];void read(int &x){char ch;for (ch=getchar();!isdigit(ch);ch=getchar());for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';}struct Tsam{map<int,int> ch[maxn];int dis[maxn],fa[maxn],root,last,tot;int newnode(int v){dis[++tot]=v;return tot;}void init(){last=root=++tot;}void add(int x){int p=last,np=newnode(dis[p]+1);last=np;for (;p&&!ch[p].count(x);p=fa[p]) ch[p][x]=np;//printf("char=%d %d\n",x,p);if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);ch[nq]=ch[q];fa[nq]=fa[q];fa[np]=fa[q]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;//printf("qp: char=%d %d %d\n",x,p,fa[p]);}}}void build(){init();for (int i=1;i<=n;i++) add(s[i]);for (int i=1;i<=n;i++) add(s[i]);}void getmin(){for (int i=1;i<=tot;i++){it iter=ch[i].begin();//printf("id=%d char=%d minson=%d\n",i,iter->first,iter->second);}for (int i=1,p=root;i<=n;i++){it iter=ch[p].begin();printf(i==n?"%d\n":"%d ",iter->first);p=iter->second;//printf("p=%d %d\n",p,ch[p].begin()->second);}}}T;int main(){//freopen("test.out","w",stdout);scanf("%d",&n);for (int i=1;i<=n;i++) read(s[i]);T.build(),T.getmin();return 0;}

bzoj3998:

对于T=0的情况,right集合不管多大它也只算出现一次

对于T=1的情况,我们一遍DP需要求出每个点的right集合大小

然后扫一遍求出每个点下面还有多少个串

然后在自动机上走一走即可

另外直接按每个点代表的最长长度排序就可以得到拓扑序了

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=1000010;using namespace std;int n,K,op;char s[maxn];struct Tsam{int dis[maxn],ri[maxn],ch[maxn][26],fa[maxn],sum[maxn],last,tot,root;int tmp[maxn],su[maxn];void init(){last=root=tot=1;}int newnode(int v){dis[++tot]=v;return tot;}void add(int x){int p=last,np=newnode(dis[p]+1);ri[np]=1,last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void prework(){init();for (int i=1;i<=n;i++) add(s[i]-'a');for (int i=1;i<=tot;i++) su[dis[i]]++;for (int i=1;i<=n;i++) su[i]+=su[i-1];for (int i=tot;i;i--) tmp[su[dis[i]]--]=i;//for (int i=1;i<=tot;i++) printf("%d ",tmp[i]);puts("");for (int i=tot;i;i--){int p=tmp[i];//printf("%d ",tmp[i]);if (op==1) ri[fa[p]]+=ri[p];else ri[p]=1;}//puts("");ri[root]=0;for (int i=tot;i;i--){int p=tmp[i];//printf("%d ",tmp[i]);sum[p]=ri[p];for (int x=0;x<26;x++){int son=ch[p][x];if (son) sum[p]+=sum[son];//printf("p=%d son=%d sum=%d\n",p,son,sum[p]);}//printf("p=%d sum=%d\n",p,sum[p]);}//puts("");}void dfs(int x,int rk){//printf("%d %d\n",x,rk);if (rk<=ri[x]) return;rk-=ri[x];for (int i=0;i<26;i++){int son=ch[x][i];if (son){if (rk<=sum[son]){putchar(i+'a');dfs(son,rk);return;}rk-=sum[son];}}}void work(){if (K>sum[root]){puts("-1");return;}dfs(root,K);puts("");}}T;int main(){scanf("%s%d%d",s+1,&op,&K);n=strlen(s+1),T.prework();T.work();return 0;}
poj1743:先考虑求一个出现次数>=2的最长子串
出现次数>=2那就只管|right(x)|>=2的点

然后对这些点的代表的子串长度取max即可

加上了不相交的限制,类似后缀数组的做法

我们就记录right集合的最大/最小值,他们的差值就是能不相交的最长长度

拿它和该点的maxs取min即可,然后对所有点的答案取max即可

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=40010;using namespace std;int n,a[maxn],tmp[maxn];bool cmp(int a,int b);struct Tsam{int fa[maxn],dis[maxn],l[maxn],r[maxn],ch[maxn][180],last,tot,root;void init(){last=root=tot=1;memset(dis,0,sizeof(dis));memset(l,63,sizeof(l));memset(r,0,sizeof(r));memset(fa,0,sizeof(fa));memset(ch,0,sizeof(ch));}int newnode(int v){dis[++tot]=v;return tot;}void add(int x){int p=last,np=newnode(dis[p]+1);last=np,l[np]=r[np]=dis[np];for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[np]=fa[q]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void work(){int ans=0;for (int i=1;i<=tot;i++) tmp[i]=i;sort(tmp+1,tmp+1+tot,cmp);for (int i=1;i<=tot;i++){l[fa[tmp[i]]]=min(l[fa[tmp[i]]],l[tmp[i]]);r[fa[tmp[i]]]=max(r[fa[tmp[i]]],r[tmp[i]]);}for (int i=1;i<=tot;i++)ans=max(ans,min(dis[i],r[i]-l[i]));printf("%d\n",ans<4?0:ans+1);}}T;bool cmp(int a,int b){return T.dis[a]>T.dis[b];}int main(){for (scanf("%d",&n);n;scanf("%d",&n)){for (int i=1;i<=n;i++) scanf("%d",&a[i]);for (int i=1;i<n;i++) a[i]=a[i+1]-a[i]+88;T.init(),n--;for (int i=1;i<=n;i++) T.add(a[i]);T.work();}return 0;}

bzoj4566

对一个串A建自动机

另一个串B在上面匹配

考虑怎么统计答案

对于当前匹配到的点,那么它parent树中的祖先代表的串现在肯定也出现了

对于每个出现的点,它代表了maxs[x]-mins[x]+1个串(mins[x]=maxs[fa[x]]+1)

这些串出现了|right(x)|次,贡献就是|right(x)|*(maxs[x]-maxs[fa[x]])

因为要统计祖先的,所以把祖先的也累加到这个节点即可

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=400010;typedef long long ll;using namespace std;int n1,n2;char s1[maxn],s2[maxn];struct Tsam{int dis[maxn],fa[maxn],ri[maxn],ch[maxn][26],last,tot,root;int tmp[maxn],v[maxn];ll sum[maxn];void init(){last=root=tot=1;}int newnode(int v){dis[++tot]=v;return tot;}void add(int x){int p=last,np=newnode(dis[p]+1);ri[np]=1,last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}//printf("tot=%d\n",tot);}void prework(){init();for (int i=1;i<=n1;i++) add(s1[i]-'a');}void Tsort(){for (int i=1;i<=tot;i++) v[dis[i]]++;for (int i=1;i<=n1;i++) v[i]+=v[i-1];for (int i=tot;i;i--) tmp[v[dis[i]]--]=i;}void DP(){Tsort();for (int i=tot;i;i--)ri[fa[tmp[i]]]+=ri[tmp[i]];//for (int i=1;i<=tot;i++) printf("fa=%d ri=%d\n",fa[i],ri[i]);for (int i=1;i<=tot;i++){int x=tmp[i];sum[x]=sum[fa[x]]+1ll*ri[x]*(dis[x]-dis[fa[x]]);//printf("sum=%lld\n",sum[x]);}}void work(){ll ans=0,len=0;for (int i=1,p=root;i<=n2;i++){int x=s2[i]-'a';if (ch[p][x]) p=ch[p][x],len++;else{for (;p&&!ch[p][x];p=fa[p]);if (!p) p=root,len=0;else len=dis[p]+1,p=ch[p][x];}if (p!=root) ans=ans+sum[fa[p]]+1ll*ri[p]*(len-dis[fa[p]]);//printf("p=%d\n",p);}printf("%lld\n",ans);}}T;int main(){scanf("%s%s",s1+1,s2+1);n1=strlen(s1+1),n2=strlen(s2+1);T.prework(),T.DP(),T.work();return 0;}/*asaaasaasasasasasasaasaaaasasasasasasaaasassssasasasasasasas 1285*/

bzoj3238:

反串的parent树是原串的后缀树
然后lcp就是他们的lca的深度,然后树形DP一下就好了


#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>typedef long long ll;const int maxn=1000010;using namespace std;int n;char s[maxn];ll ans;struct Tsam{int dis[maxn],fa[maxn],ch[maxn][26],ri[maxn],sum[maxn],last,root,tot;int su[maxn],tmp[maxn];void init(){last=root=tot=1;}int newnode(int v){dis[++tot]=v;return tot;}void add(int x){int p=last,np=newnode(dis[p]+1);ri[np]=sum[np]=1,last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void Tsort(){for (int i=1;i<=tot;i++) su[dis[i]]++;for (int i=1;i<=n;i++) su[i]+=su[i-1];for (int i=tot;i;i--) tmp[su[dis[i]]--]=i;}void treeDP(){Tsort();for (int i=tot;i;i--){int x=tmp[i];ri[fa[x]]+=ri[x];}for (int i=tot;i;i--){int x=tmp[i];ans+=1ll*sum[fa[x]]*ri[x]*dis[fa[x]];sum[fa[x]]+=ri[x];}}}T;int main(){scanf("%s",s+1),n=strlen(s+1),T.init();for (int i=n;i;i--) T.add(s[i]-'a');T.treeDP();printf("%lld\n",1ll*(n+1)*n/2*(n-1)-ans*2);return 0;}

bzoj2806:

显然答案L具有可二分性

先在后缀自动机上匹配

求出mat[i]表示以待检查的作文的每个位置i为结尾最长能匹配多长

设f[i]表示前i个字符熟悉的部分最多有多长

那么f[i]=max(f[i-1],f[j]+i-j)

其中j要满足i-j>=L&&i-j+1<=mat[i]

整理得i-mat[i]<=j<=i-L

因为mat[i]+1<=mat[i+1]

所以i+1-mat[i+1]>=i+1-(mat[i]+1)=i-mat[i]

所以i-mat[i]单调不减,于是就可以用单调队列优化了

#include<cmath>#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=1200010*2,inf=0x3f3f3f3f;using namespace std;int n,n1,n2,f[maxn],mat[maxn];char s[maxn];struct Tsam{int dis[maxn],ch[maxn][3],fa[maxn],last,root,tot;void init(){last=root=tot=1;}int newnode(int v){dis[++tot]=v;return tot;}void add(int x){int p=last,np=newnode(dis[p]+1);last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void match(){mat[0]=0;for (int i=1,p=root;i<=n;i++){int x=s[i]-'0';if (ch[p][x]) mat[i]=mat[i-1]+1,p=ch[p][x];else{for (;p&&!ch[p][x];p=fa[p]);if (!p) p=root,f[i]=0;else mat[i]=dis[p]+1,p=ch[p][x];}}//for (int i=1;i<=n;i++) printf("%d ",mat[i]);}}T;struct Tque{int v[maxn],p[maxn],head,tail;void init(){head=1,tail=0;}void push(int val,int pos){while (head<=tail&&val>v[tail]) tail--;v[++tail]=val,p[tail]=pos;}void pop(int lim){while (head<=tail&&p[head]<lim) head++;}int top(){return v[head];}}q;bool check(int lim){//printf("lim=%d\n",lim);memset(f,0,sizeof(int)*(n+5));q.init();for (int i=1;i<=n;i++){f[i]=f[i-1];if (i-lim>=0) q.push(f[i-lim]-(i-lim),i-lim);q.pop(i-mat[i]);//printf("left=%d right=%d\n",i-mat[i],i-lim);if (q.head<=q.tail) f[i]=max(f[i],q.top()+i);//printf("%d %d\n",i,q.top());}//for (int i=1;i<=n;i++) printf("%d ",f[i]);puts("");//for (int i=1;i<=n;i++) printf("%d ",mat[i]);puts("");return f[n]>=n*0.9-1e-8;}void work(){T.match();//check(5);for (;;);int l=1,r=n,mid=(l+r)>>1,ans=0;while (l<=r){if (check(mid)) l=mid+1,ans=mid;else r=mid-1;mid=(l+r)>>1;}printf("%d\n",ans);}int main(){scanf("%d%d",&n1,&n2);T.init();for (int i=1;i<=n2;i++){scanf("%s",s+1),n=strlen(s+1);for (int j=1;j<=n;j++) T.add(s[j]-'0');T.add(2);}for (int i=1;i<=n1;i++){scanf("%s",s+1),n=strlen(s+1);work();}return 0;}

bzoj2555:

一个点right集合就是它子树中所有点的right的并集

所以动态构建后缀自动机时,增加一个点,就把它的祖先的right集合都+1

因为parent树的形态会改变,所以用暴力动态树维护一下

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=1200010;using namespace std;int Q,n,mask,lastans=0;char s[maxn],op[maxn],ch[maxn];void decode(int mask){memcpy(s,ch,sizeof(char)*(n+5));for (int j=0;j<n;j++){mask=(mask*131+j)%n;char t=s[j];s[j]=s[mask];s[mask]=t;}}struct Tlct{#define ls ch[x][0]#define rs ch[x][1]int val[maxn],ch[maxn][2],fa[maxn],add[maxn];inline int which(int x){return ch[fa[x]][1]==x;}inline int isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}inline void add_tag(int x,int v){add[x]+=v,val[x]+=v;}inline void down(int x){if (add[x]!=0){if (ls) add_tag(ls,add[x]);if (rs) add_tag(rs,add[x]);add[x]=0;}}void relax(int x){if (!isroot(x)) relax(fa[x]);down(x);}void rotate(int x){int y=fa[x],z=fa[y],nx=which(x),ny=which(y),isrt=isroot(y);fa[ch[x][!nx]]=y,ch[y][nx]=ch[x][!nx];fa[x]=z;if (!isrt) ch[z][ny]=x;fa[y]=x,ch[x][!nx]=y;}void splay(int x){relax(x);while (!isroot(x)){int y=fa[x];if (isroot(y)) rotate(x);else if (which(x)==which(y)) rotate(y),rotate(x);else rotate(x),rotate(x);}}void access(int x){for (int p=0;x;x=fa[x])splay(x),rs=p,p=x;}void link(int x,int f){fa[x]=f,access(f),splay(f),add_tag(f,val[x]);}void cut(int x){access(x),splay(x),add_tag(ls,-val[x]);fa[ls]=0,ls=0;}#undef ls#undef rs}T;struct Tsam{int fa[maxn],ch[maxn][26],dis[maxn],last,root,tot;int newnode(int v){return dis[++tot]=v,tot;}void link(int p,int f){fa[p]=f,T.link(p,f);}void add(int x){int p=last,np=newnode(dis[p]+1);T.val[np]=1,last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) link(np,root);else{int q=ch[p][x];if (dis[q]==dis[p]+1) link(np,q);else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));link(nq,fa[q]),T.cut(q);link(q,nq),link(np,nq);for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void init(){last=root=tot=1;scanf("%s",s),n=strlen(s);for (int i=0;i<n;i++) add(s[i]-'A');}void insert(){for (int i=0;i<n;i++) add(s[i]-'A');}int query(){int p=root;for (int i=0;i<n;i++){int x=s[i]-'A';if (!ch[p][x]) return lastans=0;p=ch[p][x];}T.splay(p);lastans=T.val[p];return lastans;}}sam;int main(){scanf("%d",&Q),sam.init();while (Q--){scanf("%s%s",op,ch);n=strlen(ch),decode(mask);if (op[0]=='A') sam.insert();else printf("%d\n",sam.query()),mask=mask^lastans;}return 0;}

bzoj3926:

叶子数很少,所以我们可以枚举哪个叶子为根,这样就有了20个trie,对这20个trie建广义后缀自动机

然后统计有多少个不同的子串即可,不同子串个数就是Σ(dis[x]-dis[fa[x]])

<span style="font-size:14px;">#include<cstdio>#include<cstring>#include<cassert>#include<iostream>#include<algorithm>const int maxn=100010,maxm=200010,maxt=2000010;using namespace std;int n,C,col[maxn],pre[maxm],now[maxm],son[maxm],deg[maxn],tot,last;void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b,deg[b]++;}void read(int &x){char ch;for (ch=getchar();!isdigit(ch);ch=getchar());for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';}struct Tsam{int ch[maxt][11],fa[maxt],dis[maxt],root,tot;void init(){root=tot=1;}int newnode(int v){return dis[++tot]=v,tot;}void add(int x){int p=last,q=ch[p][x];if (q){if (dis[q]==dis[p]+1) last=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;last=nq;}}else{int np=newnode(dis[p]+1);for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[np]=fa[q]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}last=np;}}void work(){long long ans=0;for (int i=1;i<=tot;i++)ans+=dis[i]-dis[fa[i]];printf("%lld\n",ans);}}T;void dfs(int x,int f,int p){T.add(col[x]);int t=last;for (int y=now[x];y;y=pre[y])if (son[y]!=f)dfs(son[y],x,last),last=t;}int main(){scanf("%d%d",&n,&C),T.init();for (int i=1;i<=n;i++) read(col[i]);for (int i=1,x,y;i<n;i++) read(x),read(y),add(x,y),add(y,x);for (int i=1;i<=n;i++) if (deg[i]==1) dfs(i,0,last=T.root);T.work();return 0;}</span>

bzoj3879

对要询问的点建出虚树,然后就和AHOI差异那题基本一样了

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>typedef long long ll;const int maxn=500010*2,maxm=maxn*2;using namespace std;int n,m,a[maxn],stk[maxn],top,id[maxn],dfn[maxn],dep[maxn],tim,pw[25];ll ans;char s[maxn];bool bo[maxn];bool cmp(int a,int b){return dfn[a]<dfn[b];}void read(int &x){char ch;for (ch=getchar();!isdigit(ch);ch=getchar());for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';}struct Tgra{int pre[maxm],now[maxn],son[maxm],tot,fa[maxn][22];void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}//printf("%d %d\n",a,b);void dfs(int x){dfn[x]=++tim;for (int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1];for (int y=now[x];y;y=pre[y])dep[son[y]]=dep[x]+1,fa[son[y]][0]=x,dfs(son[y]);}int lca(int x,int y){if (dep[x]<dep[y]) swap(x,y);for (int h=dep[x]-dep[y],i=20;h;i--) if (h&pw[i]) h-=pw[i],x=fa[x][i];if (x==y) return x;for (int i=20;i>=0;i--)if (fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];return fa[x][0];}}ori;struct Tsam{int ch[maxn][26],fa[maxn],dis[maxn],cnt,last,root,tot;void init(){last=root=tot=1;}int newnode(int v){return dis[++tot]=v,tot;}int add(int x){int p=last,np=newnode(dis[p]+1);last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}return np;}}T;struct Tgraph{int pre[maxm],now[maxn],son[maxm],tot,sum[maxn];void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}//printf("connect: %d %d\n",a,b);void dfs(int x){//printf("x=%d\n",x);sum[x]=bo[x];for (int y=now[x];y;y=pre[y]){dfs(son[y]);ans+=1ll*sum[son[y]]*sum[x]*T.dis[x];sum[x]+=sum[son[y]];}now[x]=0;}}g;void init(){pw[0]=1;for (int i=1;i<=20;i++) pw[i]=pw[i-1]<<1;scanf("%d%d%s",&n,&m,s+1),T.init();//puts("%p");for (int i=n;i;i--) id[i]=T.add(s[i]-'a');for (int i=1;i<=T.tot;i++) ori.add(T.fa[i],i);ori.dfs(1);/*int qqq;scanf("%d",&qqq);for (int i=1,x,y;i<=qqq;i++) scanf("%d%d",&x,&y),printf("%d\n",ori.lca(x,y));*///for (int i=1;i<=n;i++) printf("i=%d id=%d\n",i,id[i]);}void work(){//puts("%p");int cnt;read(cnt);for (int i=1;i<=cnt;i++) read(a[i]),a[i]=id[a[i]];sort(a+1,a+1+cnt,cmp),cnt=unique(a+1,a+1+cnt)-a-1;//for (int i=1;i<=cnt;i++) printf("keypoint: %d\n",a[i]);for (int i=1;i<=cnt;i++) bo[a[i]]=1;for (int i=1;i<=cnt;i++){if (!top){stk[++top]=a[i];continue;}int u=ori.lca(a[i],stk[top]);while (top&&dep[u]<dep[stk[top]]){if (dep[u]>=dep[stk[top-1]]){g.add(u,stk[top]);if (stk[--top]!=u) stk[++top]=u;break;}--top,g.add(stk[top],stk[top+1]);}stk[++top]=a[i];}while (--top) g.add(stk[top],stk[top+1]);ans=0,g.dfs(stk[1]),printf("%lld\n",ans);for (int i=1;i<=n;i++) bo[a[i]]=0;g.tot=0;}int main(){init();for (int i=1;i<=m;i++) work();return 0;}

bzoj2780

先建出广义后缀自动机,建的时候对每个节点染色,表示它是属于哪个串

然后对每个询问串在自动机上匹配,匹配到的点即其parent树子树中的节点就可以包含这个串

于是问题就转化为给定一个颜色序列,每次询问一段区间内的颜色个数

这个就是bzoj1878HH的项链

离线,把询问按右端点排序,从左往右做,每次在树状数组中给当前位置+1,当前位置颜色的上一次出现位置-1,然后处理右端点在此的询问即可

本题一个点可能有多种颜色,所以挂个链表就好了

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=200010,maxm=200010;using namespace std;int n1,n,Q,dfn[maxn],last[maxn],tim,seq[maxn],p[maxn],ans[maxn];char s[360010];struct que{int l,r,id;}q[maxn];bool cmp(que a,que b){return a.r<b.r;}struct Tgraph{int pre[maxm],now[maxn],son[maxm],tot;void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;}//,printf("a=%d b=%d\n",a,b)void dfs(int x){dfn[x]=++tim,seq[tim]=x;for (int y=now[x];y;y=pre[y]) dfs(son[y]);last[x]=tim;}}g,chain;struct Tsam{int ch[maxn][26],fa[maxn],dis[maxn],last,root,tot;void init(){last=root=tot=1;}int newnode(int v){return dis[++tot]=v,tot;}void add(int x,int id){int p=last,q=ch[p][x];if (q){if (dis[q]==dis[p]+1) last=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;last=nq;}}else{int np=newnode(dis[p]+1);for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}last=np;}//printf("chain: ");chain.add(last,id);//printf("%d\n",tot);}}T;struct BIT{int val[maxn];void add(int x,int v){if (!x) return;for (;x<=T.tot;x+=x&(-x)) val[x]+=v;}int query(int x){int res=0;for (;x;x-=x&(-x)) res+=val[x];return res;}int query(int l,int r){//int tmp=query(r)-query(l-1);//printf("tmp=%d\n",tmp);return query(r)-query(l-1);}}bit;void match(int id){//printf("id=%d\n",id);int p=T.root;for (int i=1;i<=n;i++){int x=s[i]-'a';//printf("x=%d p=%d\n",x,p);if (T.ch[p][x]) p=T.ch[p][x];else{p=0;break;}}//printf("p=%d dfn=%d last=%d\n",p,dfn[p],last[p]);q[id]=(que){dfn[p],last[p],id};}int main(){scanf("%d%d",&n1,&Q),T.init();for (int i=1;i<=n1;i++){scanf("%s",s+1),n=strlen(s+1),T.last=1;for (int j=1;j<=n;j++) T.add(s[j]-'a',i);}for (int i=1;i<=T.tot;i++)/* printf("g: "),*/g.add(T.fa[i],i);g.dfs(1);//for (int i=1;i<=T.tot;i++) printf("id=%d dfn=%d\n",i,dfn[i]);for (int i=1;i<=Q;i++)scanf("%s",s+1),n=strlen(s+1),match(i);sort(q+1,q+1+Q,cmp);int st;for (st=1;st<=Q&&!q[st].l;st++);//printf("st=%d\n",st);for (int j=1;j<=T.tot;j++){int x=seq[j];//printf("id=%d x=%d\n",j,x);for (int y=chain.now[x];y;y=chain.pre[y]){int col=chain.son[y];bit.add(j,1);//printf("pre=%d col=%d\n",p[col],col);if (p[col]) bit.add(p[col],-1);p[col]=j;}for (;q[st].r==j;st++){ans[q[st].id]=bit.query(q[st].l,q[st].r);//printf("ans=%d %d %d\n",ans[q[st].id],q[st].l,q[st].r);}}for (int i=1;i<=Q;i++) printf("%d\n",ans[i]);return 0;}

bzoj3756:

可以得到的串就是这个trie所表示的所有串的所有子串

把这个trie建广义后缀自动机,然后就类似bzoj4566的做法,在parent树上求出一些值,然后匹配一遍,求出答案就好了

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>typedef long long ll;const int maxn=1600010,maxm=maxn;using namespace std;int n;char s[8000010];ll ans;struct Tsam{int ch[maxn][4],fa[maxn],dis[maxn],tot,last,root,v[maxn],tmp[maxn],ri[maxn];ll sum[maxn];void init(){last=root=tot=1;}int newnode(int v){return dis[++tot]=v,tot;}void add(int x){int p=last,np=newnode(dis[p]+1);ri[np]=sum[np]=1,last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void Tsort(){for (int i=1;i<=tot;i++) v[dis[i]]++;for (int i=1;i<=tot;i++) v[i]+=v[i-1];for (int i=tot;i;i--) tmp[v[dis[i]]--]=i;}void work(){Tsort();for (int i=tot;i;i--){int x=tmp[i];if (fa[x]) ri[fa[x]]+=ri[x];}for (int i=1;i<=tot;i++){int x=tmp[i];sum[x]=sum[fa[x]]+1ll*ri[x]*(dis[x]-dis[fa[x]]);}}void match(){ans=0;int len=0;for (int p=root,i=1;i<=n;i++){int x=s[i]-'a';if (ch[p][x]) len++,p=ch[p][x];else{for (;p&&!ch[p][x];p=fa[p]);if (!p) p=root,len=0;else len=dis[p]+1,p=ch[p][x];}if (fa[p]) ans+=1ll*(len-dis[fa[p]])*ri[p]+sum[fa[p]];}printf("%lld\n",ans);}}T;struct Tgraph{int pre[maxm],now[maxn],son[maxm],tot,val[maxn],last[maxn],q[maxn+10],head,tail;void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;}void bfs(){q[tail=1]=1,head=0,last[1]=T.root;while (head!=tail){int x=q[++head];for (int y=now[x];y;y=pre[y]){q[++tail]=son[y],T.last=last[x];T.add(val[y]),last[son[y]]=T.last;}}}}g;int main(){//freopen("test.in","r",stdin);freopen("test.out","w",stdout);scanf("%d",&n),T.init();for (int i=2,x;i<=n;i++)scanf("%d%s",&x,s+1),g.add(x,i,s[1]-'a');g.bfs(),scanf("%s",s+1),T.work();n=strlen(s+1),T.match();return 0;}

bzoj1396&&2865

首先只有|right(x)|=1的点才可以更新答案,设[l,r]是该点的最短的识别子串,l=maxs[x]-maxs[fa[x]],r=maxs[x]

然后我们可以发现它可以更新的区间有两段,[1,l-1]段可以用r-i+1来更新它的答案,因为我们要经过i这个位置

[l,r]段我们可以用r-l+1更新

于是我们可以开两个线段树来表示应用这两种区间覆盖,最后取min即可

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>const int maxn=1000010,maxt=maxn<<1,inf=1e9+7,pp=200000;using namespace std;int n;char s[maxn];struct Tsegment{#define ls (p<<1)#define rs (p<<1|1)#define mid ((l+r)>>1)int mins[maxt],cov[maxt];void update(int p){mins[p]=min(mins[ls],mins[rs]);}void cover(int p,int v){mins[p]=min(mins[p],v),cov[p]=min(cov[p],v);}void down(int p){if (cov[p]!=inf)cover(ls,cov[p]),cover(rs,cov[p]),cov[p]=inf;}void build(int p,int l,int r){cov[p]=mins[p]=inf;if (l==r) return;build(ls,l,mid),build(rs,mid+1,r);}void modify(int p,int l,int r,int a,int b,int v){if (l==a&&r==b){cover(p,v);return;}down(p);if (b<=mid) modify(ls,l,mid,a,b,v);else if (a>mid) modify(rs,mid+1,r,a,b,v);else modify(ls,l,mid,a,mid,v),modify(rs,mid+1,r,mid+1,b,v);update(p);}int query(int p,int l,int r,int x){if (l==x&&r==x) return mins[p];down(p);if (x<=mid) return query(ls,l,mid,x);else return query(rs,mid+1,r,x);}void modify(int l,int r,int v){modify(1,1,n,l,r,v);}//printf("l=%d r=%d v=%d\n",l,r,v);int query(int x){return query(1,1,n,x);}}t1,t2;struct Tsam{int fa[maxn-pp],ch[maxn-pp][26],ri[maxn-pp],dis[maxn-pp],last,root,tot;void init(){last=root=tot=1;}int newnode(int v){return dis[++tot]=v,tot;}void add(int x){int p=last,np=newnode(dis[p]+1);ri[np]=1,last=np;for (;p&&!ch[p][x];p=fa[p]) ch[p][x]=np;if (!p) fa[np]=root;else{int q=ch[p][x];if (dis[q]==dis[p]+1) fa[np]=q;else{int nq=newnode(dis[p]+1);memcpy(ch[nq],ch[q],sizeof(ch[q]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for (;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;}}}void work(){for (int i=1;i<=tot;i++)if (fa[i]) ri[fa[i]]=0;for (int i=1;i<=tot;i++)if (ri[i]==1){int l=dis[i]-dis[fa[i]],r=dis[i];//printf("%d %d %d %d\n",i,l,r,r-l+1);t1.modify(l,r,r-l+1);if (l>1) t2.modify(1,l-1,r);}for (int i=1;i<=n;i++) printf("%d\n",min(t1.query(i),t2.query(i)-i+1));}}T;int main(){scanf("%s",s+1),n=strlen(s+1),T.init();for (int i=1;i<=n;i++) T.add(s[i]-'a');t1.build(1,1,n),t2.build(1,1,n);T.work();return 0;}


0 0