字符串做题笔记
AC自动机:
·不要像以前一样习惯性把trie树的根设为1,从0开始的话后面getfail比较方便。
·trie树的节点编号是无序的,统计答案需要dfs或者拓扑,按编号循环显然是错的。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int S=2e6+10,N=2e5+10; int n,cnt[N],now,tot,len,tree[N][27],ed[N],num[N],fail[N]; char s[S]; int head[N],Next[N],tot1,ver[N],du[N]; void add(int x,int y){ ver[++tot1]=y; Next[tot1]=head[x]; head[x]=tot1; } void insert(int x){ now=0; len=strlen(s+1); for(int i=1;i<=len;i++){ if(!tree[now][s[i]-'a'])tree[now][s[i]-'a']=++tot; now=tree[now][s[i]-'a']; } ed[now]=1; cnt[x]=now; } queue<int>q; void getfail(){ for(int i=0;i<26;i++){ if(tree[0][i]){ fail[tree[0][i]]=0; q.push(tree[0][i]); add(tree[0][i],0); du[0]++; } } while(q.size()){ int u=q.front(); q.pop(); for(int i=0;i<26;i++){ int v=tree[u][i]; if(v){ fail[v]=tree[fail[u]][i]; q.push(v); add(v,fail[v]); du[fail[v]]++; } else{ tree[u][i]=tree[fail[u]][i]; } } } } void AC(){ now=0; len=strlen(s+1); for(int i=1;i<=len;i++){ int v=tree[now][s[i]-'a']; num[v]++; now=v; } } int main() { // freopen("1.in","r",stdin); scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%s",s+1); insert(i); } getfail(); scanf("%s",s+1); AC(); for(int i=1;i<=tot;i++){ if(!du[i])q.push(i); } while(q.size()){ int u=q.front(); q.pop(); for(int i=head[u];i;i=Next[i]){ int y=ver[i]; num[y]+=num[u]; du[y]--; if(!du[y])q.push(y); } } for(int i=1;i<=n;i++){ printf("%d\n",num[cnt[i]]); } return 0; } /* 6 a bb aa abaa abaaa abaaa abaaabaa */
·跳fail能跳到危险节点的节点,自身必为危险节点。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; int n,now,tot,len,flag; char s[30010]; int tree[30010][2],ed[30010],fail[30010]; void insert(){ now=0; len=strlen(s+1); for(int i=1;i<=len;i++){ if(!tree[now][s[i]-'0'])tree[now][s[i]-'0']=++tot; now=tree[now][s[i]-'0']; } ed[now]=1; } queue<int>q; void getfail(){ for(int i=0;i<2;i++){ fail[tree[0][i]]=0; if(tree[0][i]){ q.push(tree[0][i]); } } while(!q.empty()){ int u=q.front(); q.pop(); for(int i=0;i<2;i++){ int v=tree[u][i]; if(v){ fail[v]=tree[fail[u]][i]; if(ed[fail[v]])ed[v]=1; q.push(v); } else tree[u][i]=tree[fail[u]][i]; } } } int vis[30010]; int dfs(int x){ if(flag)return 1; if(ed[x])return 0; if(vis[x])return 1; if(x)vis[x]=1; int val=(dfs(tree[x][0])|dfs(tree[x][1])); if(val)flag=1; vis[x]=0; return val; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%s",s+1); insert(); } getfail(); if(dfs(0))printf("TAK\n"); else printf("NIE\n"); return 0; } /* 3 011 11 00000 */
·trie树里很多走不到的字母相当于回到根,转移和统计带根一起算。
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; int n,m; char s[110]; int now,len,tot,tree[6010][26],fail[6010],ed[6010]; void insert(){ now=0; len=strlen(s+1); for(int i=1;i<=len;i++){ if(!tree[now][s[i]-'A'])tree[now][s[i]-'A']=++tot; now=tree[now][s[i]-'A']; } ed[now]=1; } queue<int>q; void getfail(){ for(int i=0;i<26;i++){ if(tree[0][i]){ q.push(tree[0][i]); } } while(q.size()){ int u=q.front(); q.pop(); for(int i=0;i<26;i++){ int v=tree[u][i]; if(v){ fail[v]=tree[fail[u]][i]; q.push(v); if(ed[fail[v]])ed[v]=1; } else{ tree[u][i]=tree[fail[u]][i]; } } } } int f[110][6010],ans; void work(){ f[0][0]=1; for(int t=0;t<m;t++){ for(int i=0;i<=tot;i++){ if(ed[i])continue; for(int j=0;j<26;j++){ if(ed[tree[i][j]])continue; f[t+1][tree[i][j]]=(f[t+1][tree[i][j]]+f[t][i])%10007; } } } for(int i=0;i<=tot;i++)ans=(ans+f[m][i])%10007; } int ks(int x,int k){ int num=1; while(k){ if(k&1)num=num*x%10007; x=x*x%10007; k>>=1; } return num; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%s",s+1); insert(); } getfail(); work(); printf("%d\n",((ks(26,m)-ans)%10007+10007)%10007); return 0; }
#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<queue> using namespace std; const int N=1e5+10; char s[N]; int fa[N],lens,now,tree[N][26],tree1[N][26],num,ed[N],tot,m,fail[N],ans[N]; struct node{ int x,id; node(int a=0,int b=0){ x=a,id=b; } }; vector<node>v[N]; vector<int>e[N]; void insert(){ for(int j=1;j<=lens;j++){ if(s[j]=='B')now=fa[now]; else if(s[j]=='P'){ num++; ed[num]=now; e[now].push_back(num); } else{ if(!tree[now][s[j]-'a'])tree1[now][s[j]-'a']=tree[now][s[j]-'a']=++tot,fa[tot]=now; now=tree[now][s[j]-'a']; } } } int Next[N],head[N],tot1,ver[N]; void add(int x,int y){ ver[++tot1]=y; Next[tot1]=head[x]; head[x]=tot1; } queue<int>q0; void getfail(){ for(int i=0;i<26;i++){ if(tree[0][i]){ add(0,tree[0][i]); q0.push(tree[0][i]); } } while(q0.size()){ int u=q0.front(); q0.pop(); for(int i=0;i<26;i++){ int v=tree1[u][i]; if(v){ fail[v]=tree1[fail[u]][i]; add(fail[v],v); q0.push(v); } else tree1[u][i]=tree1[fail[u]][i]; } } } int tim,rec[N],rec1[N]; void dfs(int x){ rec[x]=++tim; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y); } rec1[x]=tim; } int tr[N]; void add1(int x,int val){ for(;x<=tim;x+=(x&-x))tr[x]+=val; } int ask(int x){ int sum=0; for(;x;x-=(x&-x))sum+=tr[x]; return sum; } void solve(int x){ for(int i=0;i<v[x].size();i++){ node y=v[x][i]; ans[y.id]=ask(rec1[ed[y.x]])-ask(rec[ed[y.x]]-1); } } void dfs1(int x){ if(e[x].size()){ for(int i=0;i<e[x].size();i++){ int y=e[x][i]; solve(y); } } for(int i=0;i<26;i++){ if(tree[x][i]){ add1(rec[tree[x][i]],1); dfs1(tree[x][i]); add1(rec[tree[x][i]],-1); } } } int main() { scanf("%s",s+1); lens=strlen(s+1); insert(); getfail(); dfs(0); scanf("%d",&m); for(int i=1,x,y;i<=m;i++){ scanf("%d%d",&x,&y); v[y].push_back(node(x,i)); } dfs1(0); for(int i=1;i<=m;i++)printf("%d\n",ans[i]); return 0; }
回文自动机:
(manacher)P3805 【模板】manacher算法
·注意细节。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; char s[23000010],a[11000010]; int len,lst=0,p[23000010],pos,ans; void manacher(){ for(int i=1;i<=len*2+1;i++){ if(lst<i){ pos=i; for(int j=i;j<=len*2+1&&2*i-j>0;j++){ if(s[j]!=s[2*i-j])break; p[i]++; lst=j; } } else{ int v=2*pos-i; if(i+p[v]-1>lst)p[i]=lst-i+1; else if(i+p[v]-1<lst)p[i]=p[v]; else{ p[i]=p[v]; while(s[i+p[i]]==s[i-p[i]])p[i]++; lst=i+p[i]-1; pos=i; } } } } int main() { scanf("%s",a+1); len=strlen(a+1); s[0]=s[1]='#'; for(int i=1;i<=len;i++){ s[i*2]=a[i]; s[i*2+1]='#'; } manacher(); for(int i=1;i<=2*len+1;i++)ans=max(ans,p[i]-1); printf("%d\n",ans); return 0; }
·注意细节,题目要求两回文串合起来才满足条件,那么边界就是不满足的。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=2e5+10; char s[N],s0[N]; int p[N],lens,l[N],r[N],ans; void manacher() { p[0]=1; int pos=0,right=0; for(int i=1;i<=lens;i++){ if(right<i){ p[i]=1; while(s[i-p[i]]==s[i+p[i]]&&i-p[i]>=0&&i+p[i]<=lens)p[i]++; right=(i+p[i]-1); pos=i; l[right]=max(l[right],p[i]-1); r[i-p[i]+1]=max(r[i-p[i]+1],p[i]-1); } else{ int j=2*pos-i; if(i+p[j]-1<right)p[i]=p[j]; else if(i+p[j]-1>right)p[i]=right-i+1; else{ p[i]=p[j]; while(s[i-p[i]]==s[i+p[i]]&&i-p[i]>=0&&i+p[i]<=lens)p[i]++; right=(i+p[i]-1); pos=i; } l[i+p[i]-1]=max(l[i+p[i]-1],p[i]-1); r[i-p[i]+1]=max(r[i-p[i]+1],p[i]-1); } } } int main() { scanf("%s",s0+1); int len=strlen(s0+1); lens=len*2; for(int i=1;i<=len;i++){ s[i*2-1]=s0[i]; s[i*2]='#'; } s[0]='#'; manacher(); for(int i=2;i<=lens;i+=2){ r[i]=max(r[i],r[i-2]-2); } for(int i=lens;i>=1;i-=2){ l[i]=max(l[i],l[i+2]-2); } for(int i=2;i<=lens;i+=2){ if(l[i]&&r[i])ans=max(ans,l[i]+r[i]); } printf("%d\n",ans); return 0; }
·从一个节点连出去新边新建节点的时候,处理完新节点的fail再把新节点赋给原节点的儿子指针,不然一定出现我跳我自己的死循环。(原因是撞在了边界上)
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int S=5e5+10; char s[S]; struct PAM{ int ch[26],len,num,fail; }a[S]; int lst,tot=1,lens,k; void build(){ a[0].fail=1,a[1].fail=1; a[0].len=0,a[1].len=-1; } int getfail(int x,int y){ while(s[y-a[x].len-1]!=s[y])x=a[x].fail; return x; } void insert(int x){ int pos=getfail(lst,x); if(!a[pos].ch[s[x]-'a']){ a[++tot].len=a[pos].len+2; a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a']; a[tot].num=a[a[tot].fail].num+1; a[pos].ch[s[x]-'a']=tot;//! } lst=a[pos].ch[s[x]-'a']; } int main() { scanf("%s",s+1); lens=strlen(s+1); build(); for(int i=1;i<=lens;i++){ s[i]=(s[i]-97+k)%26+97; insert(i); printf("%d ",a[lst].num); k=a[lst].num; } return 0; }
·和上面的AC自动机不同,回文自动机里的节点建立是有序的,可以将统计信息用循环加回fail。
·不开long long见祖宗,下次认真算一下…什么时候了还犯这问题。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; char s[300010]; struct PAM{ int ch[26],fail,len,cnt; }a[300010]; int lens,lst,tot=1; long long ans; void build(){ a[0].fail=a[1].fail=1; a[0].len=0,a[1].len=-1; } int getfail(int x,int y){ while(s[y-a[x].len-1]!=s[y])x=a[x].fail; return x; } void insert(int x){ int pos=getfail(lst,x); if(!a[pos].ch[s[x]-'a']){ a[++tot].len=a[pos].len+2; a[tot].cnt=1; a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a']; a[pos].ch[s[x]-'a']=tot; } else a[a[pos].ch[s[x]-'a']].cnt++; lst=a[pos].ch[s[x]-'a']; } int main() { scanf("%s",s+1); lens=strlen(s+1); build(); for(int i=1;i<=lens;i++)insert(i); for(int i=tot;i>1;i--){ a[a[i].fail].cnt+=a[i].cnt; ans=max(ans,1ll*a[i].len*a[i].cnt); } printf("%lld\n",ans); return 0; }
·记录一个和fail类似的指针half,表示不超过长度一半的最长后缀,减少需要跳的次数。
·从原节点的half开始寻找新节点的half,注意和新节点的len/2比较大小时要加上2,补上头尾两个字母。由此得知,当新节点的len<=2时,直接让它的half指针和fail指针相同,否则比较时出现死循环。
·困成智障就不要写题,一个+2愣是调了20min。
#include<iostream> #include<cstdio> using namespace std; int lens,lst,tot=1,ans; char s[500010]; struct PAM{ int len,fail,ch[26],half; }a[500010]; void build(){ a[0].fail=a[1].fail=1; a[0].half=a[1].half=1; a[0].len=0,a[1].len=-1; } int getfail(int x,int y){ while(s[y-a[x].len-1]!=s[y])x=a[x].fail; return x; } int gethalf(int x,int y){ while(s[y-a[x].len-1]!=s[y]||a[x].len*2+4>a[tot].len)x=a[x].fail; return x; } void insert(int x){ int pos=getfail(lst,x); if(!a[pos].ch[s[x]-'a']){ a[++tot].len=a[pos].len+2; a[tot].fail=a[getfail(a[pos].fail,x)].ch[s[x]-'a']; if(a[tot].len<=2)a[tot].half=a[tot].fail; else a[tot].half=a[gethalf(a[pos].half,x)].ch[s[x]-'a']; if((a[a[tot].half].len%2==0)&&(a[a[tot].half].len*2==a[tot].len))ans=max(ans,a[tot].len); a[pos].ch[s[x]-'a']=tot; } lst=a[pos].ch[s[x]-'a']; } int main() { scanf("%d",&lens); scanf("%s",s+1); build(); for(int i=1;i<=lens;i++){ insert(i); } printf("%d",ans); return 0; }
后缀数组:
·终于彻底理解这个排序了
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1e6+10; char s[N]; int x[N],y[N],c[N],sa[N],n,m; void getsa(){ for(int i=1;i<=n;i++)c[x[i]=s[i]]++; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)c[x[i]]++; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==n)break; m=num; } } int main() { scanf("%s",s+1); n=strlen(s+1); m=122; getsa(); for(int i=1;i<=n;i++)printf("%d ",sa[i]); return 0; }
P2852 [USACO06DEC]牛奶模式Milk Patterns
·注意细节
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int N=40010; int n,kk,ans; int s[N],x[N],y[N],sa[N],c[N],a[N]; int height[N],m,rk[N]; void getsa(){ for(int i=1;i<=n;i++)c[x[i]=s[i]]++; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)c[x[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==n)break; m=num; } } void getheight(){ for(int i=1;i<=n;i++)rk[sa[i]]=i; int k=0; for(int i=1;i<=n;i++){ if(rk[i]==1){ height[1]=k=0; continue; } if(k)k--; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[j+k]==s[i+k])k++; height[rk[i]]=k; } } int check(int x){ int num=0; for(int i=1;i<=n;i++){ if(height[i]>=x)num++; else num=0; if(num>=kk-1)return 1; } return 0; } int main() { scanf("%d%d",&n,&kk); for(int i=1;i<=n;i++)scanf("%d",&s[i]),a[i]=s[i]; sort(a+1,a+n+1); m=unique(a+1,a+n+1)-a-1; for(int i=1;i<=n;i++){ s[i]=lower_bound(a+1,a+m+1,s[i])-a; } getsa(); getheight(); int l=0,r=n; while(l<=r){ int mid=(l+r)/2; if(check(mid)){ ans=mid; l=mid+1; } else r=mid-1; } printf("%d\n",ans); return 0; }
·利用lcp(i,k)=min(height(j))(i+1<=j<=k)的性质,处理出每个height能为哪一段作出贡献。
·最后单调栈内剩下的元素的右端点要记得处理。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1e6+10; char s[N]; int n,m; int x[N],y[N],c[N],sa[N],rk[N],height[N]; long long ans; int stack[N],top,l[N],r[N]; void getsa(){ for(int i=1;i<=n;i++)c[x[i]=s[i]]++; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)c[x[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=n;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==n)break; m=num; } } void getheight(){ for(int i=1;i<=n;i++)rk[sa[i]]=i; int k=0; for(int i=1;i<=n;i++){ if(rk[i]==1){ height[1]=k=0; continue; } if(k)k--; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } int main() { scanf("%s",s+1); n=strlen(s+1); m=122; getsa(); getheight(); ans=1ll*(n-1)*n*(n+1)/2; for(int i=1;i<=n;i++){ while(top&&height[stack[top]]>=height[i])r[stack[top]]=i,top--; l[i]=stack[top]; stack[++top]=i; } while(top){ r[stack[top]]=n+1; top--; } for(int i=1;i<=n;i++){ ans-=2ll*(i-l[i])*(r[i]-i)*height[i]; } printf("%lld\n",ans); return 0; }
·check里注意符合条件的区间边界
·注意细节
#include<iostream> #include<cstdio> using namespace std; const int N=4e5+10; int n,l,r,m,lst,ans,vis[N],t[N],top,sum,tim=3866; int s[N],cnt,x[N],y[N],c[N],sa[N],height[N],rk[N],id[N],rec[N]; void getsa(){ for(int i=1;i<=cnt;i++)c[x[i]=s[i]]++; for(int i=2;i<=m;i++)c[i]+=c[i-1]; for(int i=cnt;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=cnt;k<<=1){ int num=0; for(int i=cnt-k+1;i<=cnt;i++)y[++num]=i; for(int i=1;i<=cnt;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=cnt;i++)c[x[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=cnt;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=cnt;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==cnt)break; m=num; } } void getheight(){ for(int i=1;i<=cnt;i++)rk[sa[i]]=i; int k=0; for(int i=1;i<=cnt;i++){ if(rk[i]==1){ height[1]=0; k=0; id[1]=rec[i]; continue; } if(k)k--; int j=sa[rk[i]-1]; while(s[i+k]==s[j+k]&&i+k<=cnt&&j+k<=cnt)k++; height[rk[i]]=k; id[rk[i]]=rec[i]; } } int check(int x){ while(top)vis[t[top--]]=0; sum=0; for(int i=1;i<=cnt;i++){ if(height[i]<x){ while(top)vis[t[top--]]=0; sum=0; } else{ if(!top){ vis[id[i-1]]=1; t[++top]=id[i-1]; sum++; if(sum==n)return 1; } t[++top]=id[i]; if(!vis[t[top]]){ vis[t[top]]=1; sum++; if(sum==n)return 1; } } } return 0; } int main() { scanf("%d",&n); r=N; for(int i=1,a;i<=n;i++){ scanf("%d",&a); r=min(r,a-1); for(int j=1,b;j<=a;j++){ scanf("%d",&b); if(j==1){ lst=b; continue; } s[++cnt]=b-lst+2000; rec[cnt]=i; lst=b; } s[++cnt]=++tim; rec[cnt]=i; } m=tim; getsa(); getheight(); l=1; while(l<=r){ int mid=(l+r)/2; if(check(mid)){ ans=mid; l=mid+1; } else r=mid-1; } printf("%d\n",ans+1); return 0; } //sa[c[x[y[i]]]--]=y[i]而不是=i,sa记录的是位置, y记录第二关键字对应的第一关键字的位置,i是排名 //check之前清空记录的数组 //二分注意细节
·和上一题其实一个题意,写了二分check的另一种方法
·注意作为分隔符的数字要互不相同,否则至少产生1的贡献(或者不把它们打上属于某个串的标记)。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=2e5+10; int tim,s[N],n,lst=0,x[N],y[N],c[N],m,sa[N],height[N],rk[N],rec[N],rea[N],l,r,ans; char s0[N]; void getsa(){ for(int i=1;i<=lst;i++)c[x[i]=s[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=lst;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=lst;k<<=1){ int num=0; for(int i=lst-k+1;i<=lst;i++)y[++num]=i; for(int i=1;i<=lst;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=lst;i++)c[x[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=lst;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=1; num=1; for(int i=2;i<=lst;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==lst)break; m=num; } } void getheight(){ for(int i=1;i<=lst;i++)rk[sa[i]]=i; int k=0; for(int i=1;i<=lst;i++){ if(rk[i]==1){ height[1]=k=0; rea[1]=rec[i]; continue; } if(k)k--; int j=sa[rk[i]-1]; while(j+k<=lst&&i+k<=lst&&s[i+k]==s[j+k])k++; height[rk[i]]=k; rea[rk[i]]=rec[i]; } } int t[N],top,vis[N]; int check(int x){ while(top)vis[t[top--]]=0; int sum=0; for(int i=1;i<=lst;i++){ if(height[i]<x){ while(top)vis[t[top--]]=0; sum=0; } if(!vis[rea[i]]){ vis[rea[i]]=1; t[++top]=rea[i]; sum++; if(sum==n)return 1; } } return 0; } int main(){ scanf("%d",&n); r=N; tim=30; for(int i=1;i<=n;i++){ scanf("%s",s0+1); int lens=strlen(s0+1); r=min(r,lens); for(int j=1;j<=lens;j++){ s[++lst]=s0[j]-'a'+1; rec[lst]=i; } s[++lst]=++tim; rec[lst]=i; } m=tim; getsa(); getheight(); l=0; while(l<=r){ int mid=(l+r)/2; if(check(mid)){ ans=mid; l=mid+1; } else r=mid-1; } printf("%d\n",ans); return 0; } //注意作为分隔符的字符要互不相同,否则至少会被记为1的答案
·预处理log2,不然复杂度是nlog^3n
·st表总层数不用预处理出来的log数组确定而是循环确定的话会多一层,st数组的层数再开大1
·二分的时候注意边界。
·关于答案的二分注意最长不超过min(a->b,c->d)。
·内部确定区间左右边界的二分查询最小值时要让左端点加一(height数组存的是与前一个的lcp)。
·主席树查询的时候注意要确定是否出现在区间内的点是从a->b-x+1的,因为要保证当前验证的答案长度x符合是a->b的字串。
·注意主席树的数组要开大一些。可以不用预处理T[0]。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1e5+10; int n,m,lim,ans; char s[N]; int x[N],y[N],c[N],sa[N],height[N],rk[N],tot,log[N]; int st[N][17],T[N],L[1800010],R[1800010],cnt[1800010]; void getsa(){ for(int i=1;i<=n;i++)c[x[i]=s[i]]++; for(int i=1;i<=lim;i++)c[i]+=c[i-1]; for(int i=1;i<=n;i++)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=lim;i++)c[i]=0; for(int i=1;i<=n;i++)c[x[i]]++; for(int i=1;i<=lim;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=num=1; for(int i=2;i<=n;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(n==num)break; lim=num; } } void getheight(){ for(int i=1;i<=n;i++)rk[sa[i]]=i; int k=0; for(int i=1;i<=n;i++){ if(rk[i]==1){ height[1]=k=0; continue; } if(k)k--; int j=sa[rk[i]-1]; while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } void add(int &p,int pre,int l,int r,int pos,int val){ p=++tot; L[p]=L[pre],R[p]=R[pre],cnt[p]=cnt[pre]+1; if(l==r)return; int mid=(l+r)/2; if(pos<=mid)add(L[p],L[pre],l,mid,pos,val); else add(R[p],R[pre],mid+1,r,pos,val); } int query(int p,int pre,int l,int r,int ll,int rr){ if(ll<=l&&r<=rr){ return cnt[p]-cnt[pre]; } int mid=(l+r)/2; if(rr<=mid)return query(L[p],L[pre],l,mid,ll,rr); else if(ll>mid)return query(R[p],R[pre],mid+1,r,ll,rr); else return query(L[p],L[pre],l,mid,ll,mid)+query(R[p],R[pre],mid+1,r,mid+1,rr); } void work(){ // memset(st,0x3f3f3f3f,sizeof(st)); for(int i=1;i<=n;i++)st[i][0]=height[i]; st[1][0]=N; int maxn,sum; for(int i=1;i<=log[n];i++){ int k=(1<<(i-1)); for(int j=1;j<=n-k;j++){ st[j][i]=min(st[j][i-1],st[j+k][i-1]); } } } int cal(int lon,int l,int r){ int sum=r-l+1; int val=min(st[l][log[sum]],st[r-(1<<log[sum])+1][log[sum]]); return val>=lon; } int check(int x,int a,int b,int c,int d){ int now=rk[c]; int l,r,ll=now,rr=now; l=1,r=now-1; while(l<=r){ int mid=(l+r)/2; if(cal(x,mid+1,now)){ ll=mid; r=mid-1; } else l=mid+1; } l=now+1,r=n; while(l<=r){ int mid=(l+r)/2; if(cal(x,now+1,mid)){ rr=mid; l=mid+1; } else r=mid-1; } return query(T[rr],T[ll-1],1,n,a,b-x+1); } int main(){ scanf("%d%d",&n,&m); scanf("%s",s+1); int x=1,y=0; log[1]=0; for(int i=2;i<=n;i++){ if(i==(x<<1)){ x=i; y++; } log[i]=y; } lim=122; getsa(); getheight(); for(int i=1;i<=n;i++){ add(T[i],T[i-1],1,n,sa[i],1); } work(); for(int i=1,a,b,c,d;i<=m;i++){ scanf("%d%d%d%d",&a,&b,&c,&d); int l=1,r=min(d-c+1,b-a+1); ans=0; while(l<=r){ int mid=(l+r)/2; if(check(mid,a,b,c,d)){ ans=mid; l=mid+1; } else r=mid-1; } printf("%d\n",ans); } return 0; } //预处理log2,不然复杂度是nlog^3n //st表总层数不用预处理出来的log数组确定而是循环确定的话会多一层,st数组的层数再开大1 //二分的时候注意边界。 //关于答案的二分注意最长不超过min(a->b,c->d)。 //内部确定区间左右边界的二分查询最小值时要让左端点加一(height数组存的是与前一个的lcp)。 //主席树查询的时候注意要确定是否出现在区间内的点是从a->b-x+1的,因为要保证当前验证的答案长度x符合是a->b的字串。 //注意主席树的数组要开大一些。可以不用预处理T[0]。
P2178 [NOI2015]品酒大会
·看数据范围…把初值设置到合适的大小,至少1e15肯定不行。
#include<iostream> #include<cstdio> #include<vector> #define ll long long using namespace std; const int N=6e5+10; const long long inf=1e18; ll n,m,x[N],y[N],c[N],sa[N],rk[N],height[N],fa[N]; ll a[N],ans1[N],ans=-inf,val1,ans2[N]; char s[N]; struct node{ ll maxx,minn; ll num,siz; }b[N]; vector<ll>v[N]; void getsa(){ for(int i=1;i<=n;i++)c[x[i]=s[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[i]]--]=i; for(int k=1;k<=n;k<<=1){ int num=0; for(int i=n-k+1;i<=n;i++)y[++num]=i; for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k; for(int i=1;i<=m;i++)c[i]=0; for(int i=1;i<=n;i++)c[x[i]]++; for(int i=1;i<=m;i++)c[i]+=c[i-1]; for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0; swap(x,y); x[sa[1]]=num=1; for(int i=2;i<=n;i++){ x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?num:++num; } if(num==n)break; m=num; } } void getheight(){ for(int i=1;i<=n;i++)rk[sa[i]]=i; int k=0; for(int i=1;i<=n;i++){ if(rk[i]==1){ height[1]=k=0; continue; } if(k)k--; int j=sa[rk[i]-1]; while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++; height[rk[i]]=k; } } int get(int x){ if(x==fa[x])return x; else return fa[x]=get(fa[x]); } void work(int x,int y){ // if(b[x].siz<b[y].siz)swap(x,y); fa[y]=x; ll val=max(max(b[x].num,b[y].num),max(b[x].maxx*b[y].maxx,b[x].minn*b[y].minn)); b[x].num=max(b[x].num,val); ans=max(ans,val); ll si=b[x].siz*b[y].siz; b[x].siz=b[x].siz+b[y].siz; val1+=si; b[x].maxx=max(b[x].maxx,b[y].maxx); b[x].minn=min(b[x].minn,b[y].minn); } int main() { scanf("%lld",&n); scanf("%s",s+1); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } m=122; getsa(); getheight(); for(int i=1;i<=n;i++){ b[i].maxx=b[i].minn=a[sa[i]]; b[i].siz=1; b[i].num=-inf; fa[i]=i; } for(int i=1;i<=n;i++)v[height[i]].push_back(i); for(int i=n-1;i>=0;i--){ for(int j=0;j<v[i].size();j++){ int x=v[i][j]; work(get(x),get(x-1)); } if(val1){ ans1[i]=val1; ans2[i]=ans; } } for(int i=0;i<n;i++){ printf("%lld %lld\n",ans1[i],ans2[i]); } return 0; }
后缀自动机:
·后缀链接形成一棵树。树上从叶子到根累计信息,dfs。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1e6+10; struct SAM{ int link,len,cnt; int ch[26]; }a[2*N]; int lst,siz,n; long long ans; char s[N]; int head[2*N],Next[2*N],tot,ver[2*N]; void build(){ a[0].link=-1; a[0].len=0; siz=1,lst=0; } void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } void insert(char c){ int cur=++siz; a[cur].cnt=1; a[cur].len=a[lst].len+1; int p=lst; while(p!=-1&&!a[p].ch[c-'a']){ a[p].ch[c-'a']=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; } else{ int q=a[p].ch[c-'a']; if(a[p].len+1==a[q].len){ a[cur].link=q; } else{ int clone=++siz; a[clone].len=a[p].len+1; a[clone].link=a[q].link; for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i]; while(p!=-1&&a[p].ch[c-'a']==q){ a[p].ch[c-'a']=clone; p=a[p].link; } a[q].link=a[cur].link=clone; } } lst=cur; } void dfs(int x){ for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y); a[x].cnt+=a[y].cnt; } if(a[x].cnt>1)ans=max(ans,1ll*a[x].cnt*a[x].len); } int main() { scanf("%s",s+1); n=strlen(s+1); build(); for(int i=1;i<=n;i++){ insert(s[i]); } for(int i=1;i<=siz;i++){ add(a[i].link,i); } dfs(0); printf("%lld\n",ans); return 0; }
·后缀自动机的状态数上限为2n-1,结构体开两倍。(转移数的上限为3n-4。)
·统计子串数有两种方式,dp整个自动机是其中一种,这里询问是O(n)级别所以不合适。另一种是计算所有状态对应的子串数之和,一个状态v的子串数=len(v)-len(link(v)),len为状态统计的长度len也即此状态对应的最长子串长度。由后缀链接的定义可知,每个状态对应长度连续的一组子串且短串为长串后缀,而后缀链接到的状态包括长度由len(v)到minlen(v)第一个不符合相同结束点集合的子串。设v后缀链接到的状态为z,len(z)+1=minlen(v)。由于一个状态对应的子串长度连续且到len(z)这个长度就不属于当前状态,当前状态的子串个数就是len(v)-len(z)了。
·为使转移合法而拆开状态时,子串的个数是不变的。只需要在加入新的状态并找到link之后维护答案即可。
·字符集较大,使用map。(我还离了个完全没有必要的散)
#include<iostream> #include<cstdio> #include<map> #include<algorithm> using namespace std; const int N=1e5+10; int n,m,s[N],b[N],lst,siz; long long ans; struct SAM{ int link,len; map<int,int>mp; }a[2*N]; void build(){ a[0].link=-1; a[0].len=0; lst=0,siz=1; } void insert(int c){ int cur=++siz; a[cur].len=a[lst].len+1; int p=lst; while(p!=-1&&!a[p].mp.count(c)){ a[p].mp[c]=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; ans+=a[cur].len; } else{ int q=a[p].mp[c]; if(a[p].len+1==a[q].len){ a[cur].link=q; ans+=a[cur].len-a[q].len; } else{ int clone=++siz; a[clone].len=a[p].len+1; a[clone].link=a[q].link; a[clone].mp=a[q].mp; while(p!=-1&&a[p].mp[c]==q){ a[p].mp[c]=clone; p=a[p].link; } a[q].link=a[cur].link=clone; ans+=a[cur].len-a[clone].len; } } lst=cur; } int main() { scanf("%d",&n); build(); for(int i=1;i<=n;i++){ scanf("%d",&s[i]); b[i]=s[i]; } sort(b+1,b+n+1); m=unique(b+1,b+n+1)-b-1; for(int i=1;i<=n;i++){ s[i]=lower_bound(b+1,b+m+1,s[i])-b; insert(s[i]); printf("%lld\n",ans); } return 0; }
·dp求每个点对应路径数量的时候,注意不要重复计算一个点。
·t=1时建出link树求每个状态出现次数,dp时累计进去。t=0时让每个状态的出现次数都为1,不要忘记处理。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=5e5+10; char s[N],ans[N]; int t,k,n,siz,lst,dep; struct SAM{ int len,link,cnt,num; int ch[26]; }a[N*2]; void build(){ a[0].len=0; a[0].link=-1; siz=0,lst=0; } int head[2*N],Next[2*N],tot,ver[2*N]; void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } void insert(char c){ int cur=++siz; a[cur].cnt=1; a[cur].len=a[lst].len+1; int p=lst; while(p!=-1&&!a[p].ch[c-'a']){ a[p].ch[c-'a']=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; } else{ int q=a[p].ch[c-'a']; if(a[p].len+1==a[q].len){ a[cur].link=q; } else{ int clone=++siz; a[clone].len=a[p].len+1; a[clone].link=a[q].link; for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i]; while(p!=-1&&a[p].ch[c-'a']==q){ a[p].ch[c-'a']=clone; p=a[p].link; } a[cur].link=a[q].link=clone; } } lst=cur; } void dfs(int x){ for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y); a[x].cnt+=a[y].cnt; } } void dfs1(int x){ a[x].num=a[x].cnt; for(int i=0;i<26;i++){ if(a[x].ch[i]){ if(!a[a[x].ch[i]].num)dfs1(a[x].ch[i]); a[x].num+=a[a[x].ch[i]].num; } } } void dfs2(int x,int sum){ if(x&&a[x].cnt>=sum){ return; } if(x)sum-=a[x].cnt; for(int i=0;i<26;i++){ if(a[a[x].ch[i]].num>=sum){ ans[++dep]='a'+i; dfs2(a[x].ch[i],sum); return; } else sum-=a[a[x].ch[i]].num; } } int main() { scanf("%s",s+1); n=strlen(s+1); scanf("%d%d",&t,&k); build(); for(int i=1;i<=n;i++){ insert(s[i]); } if(t==1){ for(int i=1;i<=siz;i++){ add(a[i].link,i); } dfs(0); a[0].cnt=0; } else{ for(int i=1;i<=siz;i++){ a[i].cnt=1; } } dfs1(0); if(a[0].num<k){ printf("-1\n"); return 0; } a[0].num=0; dfs2(0,k); for(int i=1;i<=dep;i++){ printf("%c",ans[i]); } return 0; }
·灵活运用反转,前缀不好处理就转化成后缀。求后缀的公共前缀=反转以后求前缀的公共后缀。两个前缀的公共后缀=link树上的lca。
·统计路径经过哪些点处理贡献=考虑每条边被多少路径经过。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1e6+10; int n,lst,siz; long long ans; char s[N]; struct SAM{ int link,cnt,len; int ch[26]; }a[N]; void build(){ a[0].len=0; a[0].link=-1; lst=0,siz=0; } int ver[N],head[N],Next[N],tot,edge[N]; void add(int x,int y,int z){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; edge[tot]=z; } void insert(char c){ int cur=++siz; a[cur].len=a[lst].len+1; a[cur].cnt=1; int p=lst; while(p!=-1&&!a[p].ch[c-'a']){ a[p].ch[c-'a']=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; } else{ int q=a[p].ch[c-'a']; if(a[p].len+1==a[q].len){ a[cur].link=q; } else{ int clone=++siz; a[clone].link=a[q].link; a[clone].len=a[p].len+1; for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i]; while(p!=-1&&a[p].ch[c-'a']==q){ a[p].ch[c-'a']=clone; p=a[p].link; } a[q].link=a[cur].link=clone; } } lst=cur; } int si[N]; void dfs(int x,int lon){ si[x]=a[x].cnt; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y,edge[i]); si[x]+=si[y]; } ans+=1ll*si[x]*(n-si[x])*lon; } int main() { scanf("%s",s+1); n=strlen(s+1); for(int i=1;i<=n/2;i++){ swap(s[i],s[n-i+1]); } build(); for(int i=1;i<=n;i++){ insert(s[i]); } for(int i=1;i<=siz;i++){ add(a[i].link,i,a[i].len-a[a[i].link].len); } dfs(0,0); printf("%lld\n",ans); return 0; }
·所有叶子节点两两之间的路径覆盖一棵树的所有子串:度为1的就是叶节点,以每个叶节点开始dfs一遍树构建广义SAM。
·广义SAM(在线)的正确写法,insert函数要比普通SAM多两个特判:
1.进入insert函数以后,判断a[lst].ch[c]&&a[lst].len+1==a[a[lst].ch[c]].len,即是否存在和要新建的状态完全等价的状态,避免重复建点。
2.新状态的link不指向0,且指向的是需要拆出来的状态时,判断a[p(last跳过link以后找到的状态)].len+1==a[cur(当前状态)].len,即拆出来的状态是不是和当前状态完全等价。如果等价,此时新节点为空节点,不承载任何独特信息,让last等于拆出来的状态。
·这是广义SAM的在线写法,离线可以建出trie树然后bfs建立SAM,dfs可能被卡成n2。离线不考虑上面的特判。
·注意细节,每次都要把last放在正确位置,以及while里不要忘记不断跳link。
#include<iostream> #include<cstdio> using namespace std; const int N=1e5+10; int n,color,col[N]; int ver[2*N],Next[2*N],head[N],tot,du[N]; long long ans; int lst,si; struct SAM{ int len,link; int ch[10]; }a[30*N]; void build(){ a[0].link=-1; } void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } void insert(int c){ if(a[lst].ch[c]&&a[lst].len+1==a[a[lst].ch[c]].len){ lst=a[lst].ch[c]; return; } int cur=++si,flag=0,clone; a[cur].len=a[lst].len+1; int p=lst; while(p!=-1&&!a[p].ch[c]){ a[p].ch[c]=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; } else{ int q=a[p].ch[c]; if(a[p].len+1==a[q].len){ a[cur].link=q; } else{ if(a[p].len+1==a[cur].len)flag=1; clone=++si; a[clone].len=a[p].len+1; a[clone].link=a[q].link; for(int i=0;i<color;i++)a[clone].ch[i]=a[q].ch[i]; while(p!=-1&&a[p].ch[c]==q){ a[p].ch[c]=clone; p=a[p].link; } a[q].link=a[cur].link=clone; } } lst=(flag?clone:cur); } void dfs1(int x,int fa){ insert(col[x]); int now=lst; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(y==fa)continue; dfs1(y,x); lst=now; } } int main() { scanf("%d%d",&n,&color); for(int i=1;i<=n;i++){ scanf("%d",&col[i]); } for(int i=1,x,y;i<n;i++){ scanf("%d%d",&x,&y); add(x,y),add(y,x); du[x]++,du[y]++; } build(); for(int i=1;i<=n;i++){ if(du[i]==1){ lst=0; dfs1(i,0); } } for(int i=1;i<=si;i++)ans+=a[i].len-a[a[i].link].len; printf("%lld\n",ans); return 0; }
·注意莫队里面指针变化的细节
·莫队是先按左端点排序分块再在块内部按右端点排序的,我写了个假莫队…
#include<iostream> #include<cstdio> #include<map> #include<algorithm> #include<vector> using namespace std; const int N=5e4+10,M=1e5+10; int n,m,siz,lst,b[M],lens; int head[M],Next[M],tot,ver[M],rec[M],rec1[M],tim,cnt,rea[M]; int ans[M],ans1,sum[M],liv[M],fir[M]; struct node{ int len,link; map<int,int>mp; vector<int>v; }a[M*2]; struct que{ int l,r,id; }e[M]; void build(){ a[0].link=-1; } void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } void insert(int c,int id){ if(a[lst].mp.count(c)&&a[a[lst].mp[c]].len==a[lst].len+1){ lst=a[lst].mp[c]; a[lst].v.push_back(id); return; } int cur=++siz; a[cur].len=a[lst].len+1; a[cur].v.push_back(id); int p=lst,flag=0,clone; while(p!=-1&&!a[p].mp.count(c)){ a[p].mp[c]=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; } else{ int q=a[p].mp[c]; if(a[p].len+1==a[q].len){ a[cur].link=q; } else{ clone=++siz; a[clone].len=a[p].len+1; a[clone].mp=a[q].mp; a[clone].link=a[q].link; if(p==lst)flag=1; while(p!=-1&&a[p].mp[c]==q){ a[p].mp[c]=clone; p=a[p].link; } a[cur].link=a[q].link=clone; } } lst=(flag?clone:cur); } void dfs(int x){ rec[x]=++tim; rea[tim]=x; for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y); } rec1[x]=tim; } void find(int num){ int now=0; for(int i=1;i<=lens;i++){ if(a[now].mp.count(b[i]))now=a[now].mp[b[i]]; else return; } e[++cnt].l=rec[now],e[cnt].r=rec1[now],e[cnt].id=num; } void putin(int x,int t){ if(sum[x]==0){ ans1++; fir[x]=t; } sum[x]++; } void del(int x,int t){ sum[x]--; if(sum[x]==0){ ans1--; liv[x]+=t-fir[x]; } } bool cmp(que a,que b){ return (a.l==b.l)?(a.r<b.r):(a.l<b.l); } int main() { scanf("%d%d",&n,&m); build(); for(int i=1,x,y;i<=n;i++){ lst=0; scanf("%d",&x); for(int j=1;j<=x;j++){ scanf("%d",&y); insert(y,i); } lst=0; scanf("%d",&x); for(int j=1;j<=x;j++){ scanf("%d",&y); insert(y,i); } } for(int i=1;i<=siz;i++)add(a[i].link,i); dfs(0); for(int i=1;i<=m;i++){ scanf("%d",&lens); for(int j=1;j<=lens;j++)scanf("%d",&b[j]); find(i); } sort(e+1,e+cnt+1,cmp); for(int i=e[1].l;i<=e[1].r;i++){ for(int j=0;j<a[rea[i]].v.size();j++){ int x=a[rea[i]].v[j]; putin(x,1); } } int l=e[1].l,r=e[1].r; ans[e[1].id]=ans1; for(int i=2;i<=cnt;i++){ int ll=e[i].l,rr=e[i].r; while(l<ll){ for(int j=0;j<a[rea[l]].v.size();j++){ int x=a[rea[l]].v[j]; del(x,i); } l++; } while(l>ll){ l--; for(int j=0;j<a[rea[l]].v.size();j++){ int x=a[rea[l]].v[j]; putin(x,i); } } while(r<rr){ r++; for(int j=0;j<a[rea[r]].v.size();j++){ int x=a[rea[r]].v[j]; putin(x,i); } } while(r>rr){ for(int j=0;j<a[rea[r]].v.size();j++){ int x=a[rea[r]].v[j]; del(x,i); } r--; } ans[e[i].id]=ans1; } for(int i=l;i<=r;i++){ for(int j=0;j<a[rea[i]].v.size();j++){ int x=a[rea[i]].v[j]; del(x,cnt+1); } } for(int i=1;i<=m;i++)printf("%d\n",ans[i]); for(int i=1;i<=n;i++)printf("%d ",liv[i]); return 0; }
SP1812 LCS2 - Longest Common Substring II
·和上面的Sandy以及公共串一样是求n个串中的最长公共字串,后缀自动机的做法要注意各种标记(min&max)进行比较的顺序。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=3e5+10; int siz,lst,lens,num=1,first,ans,maxx[N]; char s[N]; struct node{ int len,link,minn; int ch[26]; }a[N]; int ver[N],head[N],tot,Next[N]; void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } void build(){ a[0].link=-1; } void insert(char c){ int cur=++siz; a[cur].len=a[lst].len+1; a[cur].minn=N; int p=lst; while(p!=-1&&!a[p].ch[c-'a']){ a[p].ch[c-'a']=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; } else{ int q=a[p].ch[c-'a']; if(a[p].len+1==a[q].len){ a[cur].link=q; } else{ int clone=++siz; a[clone].len=a[p].len+1; a[clone].link=a[q].link; a[clone].minn=N; for(int i=0;i<26;i++)a[clone].ch[i]=a[q].ch[i]; while(p!=-1&&a[p].ch[c-'a']==q){ a[p].ch[c-'a']=clone; p=a[p].link; } a[cur].link=a[q].link=clone; } } lst=cur; } void change(){ int now=0,l=0; for(int i=1;i<=lens;i++){ while(!a[now].ch[s[i]-'a']&&now!=0){ now=a[now].link; l=min(l,a[now].len); } if(!now)l=0; if(a[now].ch[s[i]-'a']){ now=a[now].ch[s[i]-'a']; l++; l=min(l,a[now].len); maxx[now]=max(maxx[now],l); } } } void dfs(int x){ for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y); maxx[x]=max(maxx[x],maxx[y]); } a[x].minn=min(a[x].minn,min(maxx[x],a[x].len)); } void dfs1(int x){ ans=max(ans,a[x].minn); for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs1(y); } } int main(){ scanf("%s",s+1); build(); lens=strlen(s+1); first=lens; for(int i=1;i<=lens;i++){ insert(s[i]); } for(int i=1;i<=siz;i++)add(a[i].link,i); while(scanf("%s",s+1)!=EOF){ memset(maxx,0,sizeof(maxx)); num++; lens=strlen(s+1); change(); dfs(0); } if(num==1)printf("%d\n",first); else{ dfs1(0); printf("%d\n",ans); } return 0; }
附带Sandy的卡片的后缀自动机版本
#include<iostream> #include<cstdio> #include<cstring> #include<map> using namespace std; const int N=3e5+10; int fro,n,siz,lst,lens,num=1,first,ans,maxx[N]; int s[N]; struct node{ int len,link,minn; map<int,int>mp; }a[N]; int ver[N],head[N],tot,Next[N]; void add(int x,int y){ ver[++tot]=y; Next[tot]=head[x]; head[x]=tot; } void build(){ a[0].link=-1; } void insert(int c){ int cur=++siz; a[cur].len=a[lst].len+1; a[cur].minn=N; int p=lst; while(p!=-1&&!a[p].mp.count(c)){ a[p].mp[c]=cur; p=a[p].link; } if(p==-1){ a[cur].link=0; } else{ int q=a[p].mp[c]; if(a[p].len+1==a[q].len){ a[cur].link=q; } else{ int clone=++siz; a[clone].len=a[p].len+1; a[clone].link=a[q].link; a[clone].minn=N; a[clone].mp=a[q].mp; while(p!=-1&&a[p].mp[c]==q){ a[p].mp[c]=clone; p=a[p].link; } a[cur].link=a[q].link=clone; } } lst=cur; } void change(){ int now=0,l=0; for(int i=1;i<=lens;i++){ while(!a[now].mp.count(s[i])&&now!=0){ now=a[now].link; l=min(l,a[now].len); } if(!now)l=0; if(a[now].mp.count(s[i])){ now=a[now].mp[s[i]]; l++; l=min(l,a[now].len); maxx[now]=max(maxx[now],l); } } } void dfs(int x){ for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs(y); maxx[x]=max(maxx[x],maxx[y]); } a[x].minn=min(a[x].minn,min(maxx[x],a[x].len)); } void dfs1(int x){ ans=max(ans,a[x].minn); for(int i=head[x];i;i=Next[i]){ int y=ver[i]; dfs1(y); } } int main(){ build(); scanf("%d",&n); scanf("%d",&lens); first=lens; for(int i=1,x;i<=lens;i++){ scanf("%d",&x); if(i!=1)insert(x-fro); fro=x; } for(int i=1;i<=siz;i++)add(a[i].link,i); for(int i=2;i<=n;i++){ scanf("%d",&lens); for(int j=1,x;j<=lens;j++){ scanf("%d",&x); if(j!=1)s[j-1]=x-fro; fro=x; } memset(maxx,0,sizeof(maxx)); num++; change(); dfs(0); } if(num==1)printf("%d\n",first); else{ dfs1(0); printf("%d\n",ans+1); } return 0; }
刚刚忘记留空行了(´;ω;`)编辑不能
持续补完
(给博客园的延迟烧个高香。)