AC自动机:
P5357 【模板】AC自动机(二次加强版)
·不要像以前一样习惯性把trie树的根设为1,从0开始的话后面getfail比较方便。
·trie树的节点编号是无序的,统计答案需要dfs或者拓扑,按编号循环显然是错的。
#include#include #include #include 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 */
P2444 [POI2000]病毒
·跳fail能跳到危险节点的节点,自身必为危险节点。
#include#include #include #include 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 */
P4052 [JSOI2007]文本生成器
·trie树里很多走不到的字母相当于回到根,转移和统计带根一起算。
#include#include #include #include 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 ){ 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; }
P2414 [NOI2011]阿狸的打字机
·这题具体写个题解吧
#include#include #include #include #include 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 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 ){ 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 ){ 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; }
回文自动机:
(前置)P3805 【模板】manacher算法
·注意细节。
#include#include #include 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 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; }
P5496 【模板】回文自动机(PAM)
·从一个节点连出去新边新建节点的时候,处理完新节点的fail再把新节点赋给原节点的儿子指针,不然一定出现我跳我自己的死循环。(原因是撞在了边界上)
#include#include #include 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; }
P3649 [APIO2014]回文串
·和上面的AC自动机不同,回文自动机里的节点建立是有序的,可以将统计信息用循环加回fail。
·不开long long见祖宗,下次认真算一下…什么时候了还犯这问题。
#include#include #include 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; }
P4287 [SHOI2011]双倍回文
·记录一个和fail类似的指针half,表示不超过长度一半的最长后缀,减少需要跳的次数。
·从原节点的half开始寻找新节点的half,注意和新节点的len/2比较大小时要加上2,补上头尾两个字母。由此得知,当新节点的len<=2时,直接让它的half指针和fail指针相同,否则比较时出现死循环。
·困成智障就不要写题,一个+2愣是调了20min。
#include#include 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; }
后缀数组:
P3809 【模板】后缀排序
·终于彻底理解这个排序了
#include#include #include 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#include #include 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; }
P4248 [AHOI2013]差异(后缀数组)
·利用lcp(i,k)=min(height(j))(i+1<=j<=k)的性质,处理出每个height能为哪一段作出贡献。
·最后单调栈内剩下的元素的右端点要记得处理。
#include#include #include 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; }
后缀自动机:
P3804 【模板】后缀自动机
·后缀链接形成一棵树。树上从叶子到根累计信息,dfs。
#include#include #include 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; }
P4070 [SDOI2016]生成魔咒
·后缀自动机的状态数上限为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#include #include
P3975 [TJOI2015]弦论
·dp求每个点对应路径数量的时候,注意不要重复计算一个点。
·t=1时建出link树求每个状态出现次数,dp时累计进去。t=0时让每个状态的出现次数都为1,不要忘记处理。
#include#include #include 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; }
P4248 [AHOI2013]差异(后缀自动机)
·灵活运用反转,前缀不好处理就转化成后缀。求后缀的公共前缀=反转以后求前缀的公共后缀。两个前缀的公共后缀=link树上的lca。
·统计路径经过哪些点处理贡献=考虑每条边被多少路径经过。
#include#include #include 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; }
P3346 [ZJOI2015]诸神眷顾的幻想乡
·所有叶子节点两两之间的路径覆盖一棵树的所有子串:度为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#include 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 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 ){ 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; }
刚刚忘记留空行了(´;ω;`)编辑不能
持续补完
(给博客园的延迟烧个高香。)