SAM 后缀自动机
bzoj3676 回文串
题目大意:给定一个字符串,求其中某种回文串的长度*出现次数的最大值。
思路:建立后缀自动机,用manachur求出本质不同的回文串(也就是比较使pp[i]+1的时候),然后在后缀自动机上的相应节点往上找fa,统计siz。
(这道题目中manacher不能加字符(会mle),所以要奇偶分别匹配,但是偶数的时候是有问题的。)
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 600005 #define maxsiz 26 #define up 20 #define LL long long using namespace std; char ss[N]; int sz,last,ll,pp[N]={0}; LL ans=0LL; struct use{ int ch[N][maxsiz],fa[N],mx[N],siz[N],que[N],id[N],anc[N][up]; int idx(char ch){return ch-'a';} void extend(char x,int ii){ int i,j,u,p,q,np,nq; u=idx(x);p=last;id[ii]=np=++sz; mx[np]=mx[p]+1;siz[np]=1; while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];} if (!p) fa[np]=1; else{ q=ch[p][u]; if (mx[p]+1==mx[q]) fa[np]=q; else{ nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];} } }last=np;} void pre(){ int i,j; memset(pp,0,sizeof(pp)); for (i=1;i<=sz;++i) ++pp[mx[i]]; for (i=1;i<=ll;++i) pp[i]+=pp[i-1]; for (i=sz;i;--i) que[pp[mx[i]]--]=i; for (i=sz;i;--i) siz[fa[que[i]]]+=siz[que[i]]; memset(anc[0],0,sizeof(anc[0])); for (i=1;i<=sz;++i){ anc[que[i]][0]=fa[que[i]]; for (j=1;j<up;++j) anc[que[i]][j]=anc[anc[que[i]][j-1]][j-1]; }} void query(int l,int r){ int u,v,i,j;u=id[r]; for (i=up-1;i>=0;--i) if (mx[anc[u][i]]>=r-l+1) u=anc[u][i]; ans=max(ans,(LL)siz[u]*(LL)(r-l+1)); } }sam; void work(){ int i,j,mx,id; memset(pp,0,sizeof(pp)); for (mx=0,i=1;i<ll;++i){ if (mx>i) pp[i]=min(pp[2*id-i],mx-i); else{pp[i]=1;sam.query(i,i);} for (;ss[i-pp[i]]==ss[i+pp[i]];++pp[i]) sam.query(i-pp[i],i+pp[i]); if (pp[i]+i>mx){mx=pp[i]+i;id=i;} }memset(pp,0,sizeof(pp)); for (mx=0,i=1;i<ll;++i){ if (mx>i) pp[i]=min(pp[2*id-i-1],mx-i); else pp[i]=0; for (;ss[i-pp[i]]==ss[i+pp[i]+1];++pp[i]) sam.query(i-pp[i],i+pp[i]+1); if (pp[i]+i>mx){mx=pp[i]+i;id=i;} } } int main(){ int i,j,n; scanf("%s",ss+1);ss[0]='&'; ll=strlen(ss+1);ss[++ll]='#'; for (sz=last=1,i=1;i<ll;++i) sam.extend(ss[i],i); sam.pre();work();printf("%I64d\n",ans); }
bzoj3926 诸神眷顾的幻想乡
题目大意:给定一棵树和每个节点的颜色,求颜色序列不同的链的个数(AB和BA是不同的)。
思路:因为题目保证了每个节点最多有20个相邻节点,所以可以从每个叶子dfs,然后加点,这里有一个小优化:如果这个节点和父亲的关系没变就可以不用新开节点。对于每个叶子节点出去的串都是从根开始建的,最后答案就是sigma mx[i]-mx[fa[i]]。
注意:因为一个点会加入多次,数组要开的足够大。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 200005 #define M 2000005 #define ms 10 #define LL long long using namespace std; int point[N]={0},next[N],en[N],co[N],tot=0,c,du[N],sz,cnt[M]={0},id[N]; struct use{ int ch[M][ms],fa[M],mx[M],siz[M],que[M]; int extend(int pp,int u){ int i,j,p,np,q,nq;np=ch[p=pp][u]; if (np && mx[np]==mx[pp]+1) return np; np=++sz;mx[np]=mx[p]+1;siz[np]=1; while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];} if (!p) fa[np]=1; else{ q=ch[p][u]; if (mx[q]==mx[p]+1) fa[np]=q; else{ nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];} } }return np;} LL calc(){ int i,j;LL ans=0; for (i=2;i<=sz;++i) ans+=(LL)(mx[i]-mx[fa[i]]); return ans;} }sam; void add(int u,int v){ next[++tot]=point[u];point[u]=tot;en[tot]=v; next[++tot]=point[v];point[v]=tot;en[tot]=u; ++du[u];++du[v];} void dfs(int u,int ff,int len){ int i,j,v;id[u]=sam.extend(id[ff],co[u]); for (i=point[u];i;i=next[i]){ if ((v=en[i])==ff) continue; dfs(v,u,len+1); } } int main(){ int i,j,u,v,n,m;scanf("%d%d",&n,&c); for (i=1;i<=n;++i) scanf("%d",&co[i]); for (i=1;i<n;++i){scanf("%d%d",&u,&v);add(u,v);} sz=id[0]=1; for (i=1;i<=n;++i) if (du[i]==1) dfs(i,0,1); printf("%I64d\n",sam.calc()); }
bzoj3998 弦论
题目大意:求给定字符串第k小的字串(T=0的时候,不同位置的相同子串算一个;T=1的时候,不同位置的相同子串算多个)。
思路:建后缀自动机,求right集合大小(siz),更新出每个点后面有多少个子串(两种),T=0的是1、T=1的是siz[i],1号点的都是0。然后每位每位的求就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 1000005 #define M 5000000 #define mz 26 #define LL long long using namespace std; int last,sz,pp[N]={0},l,point[N],next[M],en[M],du[N]={0},tot=0; char ss[N]; void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;++du[v];} struct use{ int ch[N][mz],fa[N],mx[N],que[N]; LL siz[N],fi[2][N]; int idx(char x){return x-'a';} void extend(char x){ int u,p,q,np,nq; u=idx(x);p=last;np=++sz; mx[np]=mx[p]+1;siz[np]=1LL; while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];} if (!p) fa[np]=1; else{ q=ch[p][u]; if (mx[p]+1==mx[q]) fa[np]=q; else{ nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];} } }last=np;} void pre(){ int i,j; for (i=1;i<=sz;++i){ ++pp[mx[i]]; for (j=0;j<mz;++j) if (ch[i][j]) add(ch[i][j],i); }for (i=1;i<=l;++i) pp[i]+=pp[i-1]; for (i=sz;i;--i) que[pp[mx[i]]--]=i; for (i=sz;i;--i) siz[fa[que[i]]]+=siz[que[i]]; } void topu(){ int i,u,v,head=0,tail=0; for (i=1;i<=sz;++i){ if (!du[i]) que[++tail]=i; fi[0][i]=(i==1 ? 0LL : 1LL); fi[1][i]=(i==1 ? 0LL : siz[i]); }while(head<tail){ u=que[++head]; for (i=point[u];i;i=next[i]){ if (!(--du[v=en[i]])) que[++tail]=v; fi[0][v]+=fi[0][u];fi[1][v]+=fi[1][u]; } } } int work(int t,LL k){ int i,u,v;l=0; if (k>fi[t][u=1]) return 0; while(u){ if (k<=0) return 1; for (i=0;i<mz;++i){ if (!(v=ch[u][i])) continue; if (k>fi[t][v]) k-=fi[t][v]; else{ ss[l++]=i+'a';u=v; k-=(!t ? 1 : siz[u]); break;} }if (i>=mz) break; }return 1;} }sam; int main(){ int i,t;LL k;scanf("%s",ss);l=strlen(ss); for (i=0,sz=last=1;i<l;++i) sam.extend(ss[i]); sam.pre();sam.topu(); scanf("%d%I64d",&t,&k); if (!sam.work(t,k)) printf("-1\n"); else{ for (i=0;i<l;++i) putchar(ss[i]); printf("\n");} }
bzoj4032 最短不公共子串
题目大意:求a的最短的子串/子序列,不是b的子串/子序列。
思路:1)a的子串、b的子串,fi[i][j]表示a到i、b到j的最长公共子串,答案是i和所有j的最大值的最小值+1;2)a的子串、b的子序列,fi[i][j]表示a到i、b到j的最长公共子串,更新的时候略有不同,可以由x=0~j-1的fi[i-1][x]更新fi[i][j],最后答案还是最大值的最小值+1;3)a的子序列、b的子串,对b建后缀自动机,在后缀自动机上dp,ff[i]表示匹配到i这个节点和a的最大匹配长度,答案是ff[i]的最小值+1;4)a的子序列、b的子序列,ff[i]表示b到i和a的最大匹配长度,类似背包优化,第二维倒着循环,记录b中每位之后每个字母的最前出现位置,然后更新,答案是ff[i]的最小值+1。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 4005 #define mz 26 using namespace std; int la,lb,fi[N][N],last,sz,gi[N][mz],cc[mz],ff[N]; char a[N],b[N]; int calc1(){ int i,j,ci,mn;mn=N; memset(fi,0,sizeof(fi)); for (i=1;i<=la;++i){ for (ci=0,j=1;j<=lb;++j){ if (a[i]==b[j]) fi[i][j]=fi[i-1][j-1]+1; ci=max(ci,fi[i][j]); }if (ci!=i) mn=min(mn,ci); }return (mn==N ? -1 : mn+1); } int calc2(){ int i,j,bi,ci,mn;mn=N; memset(fi,0,sizeof(fi)); for (i=1;i<=la;++i){ for (bi=ci=0,j=1;j<=lb;++j){ if (a[i]==b[j]) fi[i][j]=bi+1; ci=max(ci,fi[i][j]); bi=max(bi,fi[i-1][j]); }if (ci!=i) mn=min(mn,ci); }return (mn==N ? -1 : mn+1); } struct use{ int fa[N],ch[N][mz],mx[N],po[N]; int idx(char ch){return ch-'a';} void extend(char c,int x){ int u,p,q,np,nq; u=idx(c);p=last;np=++sz; mx[np]=mx[p]+1; while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];} if (!p) fa[np]=1; else{ q=ch[p][u]; if (mx[q]==mx[p]+1) fa[np]=q; else{ nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];} } }last=np;po[x]=np; } int calc3(){ int i,j,k,mn,ci;mn=N;last=sz=1; for (i=1;i<=lb;++i) extend(b[i],i); memset(ff,60,sizeof(ff));ff[1]=0; for (i=1;i<=la;++i) for (j=1;j<=sz;++j){ if (!(ci=ch[j][idx(a[i])])) mn=min(mn,ff[j]); else ff[ci]=min(ff[ci],ff[j]+1); } return (mn==N ? -1 : mn+1); } }sam; int calc4(){ int i,j,mn,ci;mn=N; for (i=0;i<mz;++i) cc[i]=lb+1; for (i=lb;i>=0;--i){ for (j=0;j<mz;++j) gi[i][j]=cc[j]; cc[b[i]-'a']=i; }memset(ff,60,sizeof(ff));ff[0]=0; for (i=1;i<=la;++i) for (j=lb;j>=0;--j){ if ((ci=gi[j][a[i]-'a'])>lb) mn=min(mn,ff[j]); else ff[ci]=min(ff[ci],ff[j]+1); } return (mn==N ? -1 : mn+1); } int main(){ scanf("%s%s",a+1,b+1);la=strlen(a+1);lb=strlen(b+1); printf("%d\n%d\n%d\n%d\n",calc1(),calc2(),sam.calc3(),calc4()); }
bzoj4180 字符串计数
题目大意:一个字符串S只由ABCD组成,每次从S中找一个字串加入T,求一个长度为n的最小操作次数的最大值。
思路:考虑二分操作次数,然后判断当前操作次数下最小的字符串长度。fi[i][j]表示i开头j结尾的最短长度(因为考虑转移,所以表示成i开头,j结尾就不是字串的最短长度比较好做),这个数组可以用后缀自动机拓扑一边求出来,但因为是i开头j结尾不是字串的长度,所以要从rt开始的串才行。对于判断,矩乘,然后看能否有一个i开头j结尾的长度小于n。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 500000 #define LL long long #define inf 0x7fffffffffffffffLL using namespace std; char ss[N]; LL n; int last,sz; char in(){ char ch=getchar(); while(ch<'A'||ch>'Z') ch=getchar(); return ch;} struct mat{ LL num[4][4]; void init(){ memset(num,0,sizeof(num)); for (int i=0;i<4;++i) num[i][i]=1LL;} mat operator*(const mat &x)const{ int i,j,k;mat c; for (i=0;i<4;++i) for (j=0;j<4;++j){ c.num[i][j]=inf; for (k=0;k<4;++k) c.num[i][j]=min(c.num[i][j],num[i][k]+x.num[k][j]); }return c;} }ji; mat mi(mat x,LL y){ mat a;a.init(); for (;y;y/=2LL){ if (y%2LL) a=a*x; x=x*x; }return a;} struct use{ int fa[N],ch[N][4],mx[N];LL mn[N][4]; bool vi[N]; void init(){ memset(fa,0,sizeof(fa)); memset(ch,0,sizeof(ch)); memset(mx,0,sizeof(mx)); memset(vi,false,sizeof(vi));} int idx(char u){return u-'A';} void extend(char c){ int u,p,q,np,nq;u=idx(c); p=last;np=++sz;mx[np]=mx[p]+1; while(p && !ch[p][u]){ch[p][u]=np;p=fa[p];} if (!p) fa[np]=1; else{ q=ch[p][u]; if (mx[q]==mx[p]+1) fa[np]=q; else{ nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];fa[q]=fa[np]=nq; while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];} } }last=np;} void dfs(int u){ if (vi[u]) return;vi[u]=true; int i,j; for (i=0;i<4;++i){ if (ch[u][i]) dfs(ch[u][i]); mn[u][i]=inf/3LL; }for (i=0;i<4;++i){ if (!ch[u][i]){mn[u][i]=1LL;continue;} for (j=0;j<4;++j) mn[u][j]=min(mn[u][j],mn[ch[u][i]][j]+1LL); } } }sam; bool judge(LL x){ int i,j; mat ci=mi(ji,x); for (i=0;i<4;++i) for (j=0;j<4;++j) if (ci.num[i][j]+1LL<=n) return true; return false;} int main(){ int i,j,ll;LL l,r,mid,ans;scanf("%I64d",&n); scanf("%s",ss);ll=strlen(ss);sam.init(); for (last=sz=1,i=0;i<ll;++i) sam.extend(ss[i]); sam.dfs(1);l=0;r=n+1; for (i=0;i<4;++i) for (j=0;j<4;++j) ji.num[i][j]=sam.mn[sam.ch[1][i]][j]; while(l<=r){ mid=(l+r)/2LL; if (judge(mid)){l=mid+1;ans=mid;} else r=mid-1; }printf("%I64d\n",ans+1); }
bzoj4545 DQS的trie
题目大意:维护一个trie,询问trie上本质不同的子串个数和某种串出现的个数。
思路:建后缀自动机,用lct维护子树的大小的方法是在链上放标记(维护一些点的权值表示子树大小)。全局不同子串的个数用全局变量=mx[x]-mx[fa[x]]就可以了;某个串出现次数是从后缀自动机上顺着走,最后一个点的子树大小就是答案。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define N 200005 #define up 3 #define LL long long using namespace std; int in(){ char ch=getchar();int x=0; while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9'){ x=x*10+ch-'0';ch=getchar(); }return x;} int chin(){ char ch=getchar(); while(ch<'a'||ch>'z') ch=getchar(); return ch-'a';} int point[N],next[N],en[N],va[N],tot=0,ff[N]={0},id[N]={0},sz,co[N]; char ss[N]; LL ans=0LL; void add(int u,int v,int c){ next[++tot]=point[u];point[u]=tot;en[tot]=v;va[tot]=c; next[++tot]=point[v];point[v]=tot;en[tot]=u;va[tot]=c;} struct lct{ int fa[N],ch[N][2],sz[N],del[N],que[N]; bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;} bool isd(int x){return ch[fa[x]][1]==x;} void paint(int x,int y){if (x){del[x]+=y;sz[x]+=y;}} void pushdown(int x){ int l,r;l=ch[x][0];r=ch[x][1]; if (del[x]){paint(l,del[x]);paint(r,del[x]);del[x]=0;} } void rotate(int x){ int y,z,l,r;z=fa[y=fa[x]]; if (ch[y][0]==x) l=0; else l=1; r=l^1; if (!isroot(y)){ if (y==ch[z][0]) ch[z][0]=x; else ch[z][1]=x; }fa[x]=z;fa[y]=x;fa[ch[x][r]]=y; ch[y][l]=ch[x][r];ch[x][r]=y; } void splay(int x){ int i,y,z,qz;que[qz=1]=x; for (i=x;!isroot(i);i=fa[i]) que[++qz]=fa[i]; for (;qz;--qz){ pushdown(que[qz]); }while(!isroot(x)){ z=fa[y=fa[x]]; if (!isroot(y)){ if (isd(y)==isd(x)) rotate(y); else rotate(x); }rotate(x); } } void access(int x){int y=0;while(x){splay(x);ch[x][1]=y;y=x;x=fa[x];}} void link(int x,int y){fa[x]=y;access(y);splay(y);paint(y,sz[x]);} void cut(int x){ access(x);splay(x);paint(ch[x][0],-sz[x]); fa[ch[x][0]]=0;ch[x][0]=0; } }tr; struct use{ int fa[N],ch[N][up],mx[N]; int extend(int pp,int u){ int i,j,p,np,q,nq;p=pp; mx[np=++sz]=mx[p]+1;tr.sz[np]=1; while(p&&!ch[p][u]){ch[p][u]=np;p=fa[p];} if (!p){fa[np]=1;tr.link(np,1);ans+=(LL)(mx[np]-mx[1]);} else{ q=ch[p][u]; if (mx[q]==mx[p]+1){fa[np]=q;ans+=(LL)(mx[np]-mx[q]);tr.link(np,q);} else{ nq=++sz;mx[nq]=mx[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q];tr.link(nq,fa[q]); ans+=(LL)(mx[nq]-mx[fa[nq]]); ans-=(LL)(mx[q]-mx[fa[q]]); tr.cut(q);fa[q]=fa[np]=nq; ans+=(LL)(mx[q]-mx[nq]+mx[np]-mx[nq]); tr.link(q,nq);tr.link(np,nq); while(ch[p][u]==q){ch[p][u]=nq;p=fa[p];} } }return np;} int query(){ int i,l,u=1;l=strlen(ss); for (i=0;i<l;++i){ if (!ch[u][ss[i]-'a']) return 0; u=ch[u][ss[i]-'a']; }tr.splay(u);return tr.sz[u]; } }sam; void dfs(int u,int f,int k){ int i,v;ff[u]=f; if (k){ id[u]=sam.extend(id[f],co[u]); }for (i=point[u];i;i=next[i]){ if ((v=en[i])==f||ff[v]) continue; co[v]=va[i];dfs(v,u,k+1); } } int main(){ int n,i,j,m,u,v,c,op,rt,si; in();n=in(); for (i=1;i<n;++i){ u=in();v=in();c=chin(); add(u,v,c); }sz=id[1]=1;dfs(1,0,0);m=in(); while(m--){ op=in(); if (op==1) printf("%I64d\n",ans); if (op==2){ rt=in();si=in(); for (i=1;i<si;++i){ u=in();v=in();c=chin(); add(u,v,c); }dfs(rt,ff[rt],0); }if (op==3){ scanf("%s",ss); printf("%d\n",sam.query()); } } }