zoukankan      html  css  js  c++  java
  • [学习笔记]后缀自动机SAM

    好抽象啊,早上看了两个多小时才看懂,(\%\%\%Fading) 早就懂了

    讲解就算了吧……可以去看看其他人的博客

    1、【模板】后缀自动机

    (siz) 为该串出现的次数,(l) 为子串长度,每次乘一下就好了

    (Code Below:)

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=2000000+10;
    int n,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn];
    char s[maxn];ll ans;
    
    void insert(int c){
    	int p=last,q=++cnt;last=q;l[q]=l[p]+1;
    	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    	if(!p) fa[q]=1;
    	else {
    		int r=ch[p][c];
    		if(l[p]+1==l[r]) fa[q]=r;
    		else {
    			int s=++cnt;l[s]=l[p]+1;
    			memcpy(ch[s],ch[r],sizeof(ch[r]));
    			fa[s]=fa[r];fa[r]=fa[q]=s;
    			for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
    		}
    	}
    	siz[q]=1;
    }
    
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
    	for(int i=1;i<=n;i++) insert(s[i]-'a');
    	for(int i=1;i<=cnt;i++) c[l[i]]++;
    	for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
    	for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
    	for(int i=cnt;i>=1;i--){
    		siz[fa[a[i]]]+=siz[a[i]];
    		if(siz[a[i]]>1) ans=max(ans,1ll*siz[a[i]]*l[a[i]]);
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    题意简述:([TJOI2015]) 弦论找第 (k) 小子串 (t=0) 的弱化版

    直接像权值线段树找第 (k) 小就好了

    (Code Below:)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=200000+10;
    int n,q,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn];
    char s[maxn];
    
    void insert(int c){
    	int p=last,q=++cnt;last=q;l[q]=l[p]+1;
    	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    	if(!p) fa[q]=1;
    	else {
    		int r=ch[p][c];
    		if(l[p]+1==l[r]) fa[q]=r;
    		else {
    			int s=++cnt;l[s]=l[p]+1;
    			memcpy(ch[s],ch[r],sizeof(ch[r]));
    			fa[s]=fa[r];fa[r]=fa[q]=s;
    			for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
    		}
    	}
    }
    
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
    	for(int i=1;i<=n;i++) insert(s[i]-'a');
    	for(int i=1;i<=cnt;i++) c[l[i]]++;
    	for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
    	for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
    	for(int i=cnt;i>=1;i--){
    		siz[a[i]]=1;
    		for(int j=0;j<26;j++)
    			if(ch[a[i]][j]) siz[a[i]]+=siz[ch[a[i]][j]];
    	}
    	scanf("%d",&q);
    	int k,p;
    	while(q--){
    		scanf("%d",&k);p=1;
    		while(k){
    			for(int i=0;i<26;i++){
    				if(!ch[p][i]) continue;
    				if(siz[ch[p][i]]>=k){
    					putchar(i+'a');
    					k--;p=ch[p][i];break;
    				}
    				else k-=siz[ch[p][i]];
    			}
    		}
    		putchar('
    ');
    	}
    	return 0;
    }
    

    3、[TJOI2015]弦论

    (t=1) 的时候就是像模板一样记录一下 (siz),拓扑一遍

    (Code Below:)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1000000+10;
    int n,q,t,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn],sum[maxn];
    char s[maxn];
    
    void insert(int c){
        int p=last,q=++cnt;last=q;l[q]=l[p]+1;
        for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
        if(!p) fa[q]=1;
        else {
            int r=ch[p][c];
            if(l[p]+1==l[r]) fa[q]=r;
            else {
                int s=++cnt;l[s]=l[p]+1;
                memcpy(ch[s],ch[r],sizeof(ch[r]));
                fa[s]=fa[r];fa[r]=fa[q]=s;
                for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
            }
        }
        siz[q]=1;
    }
    
    int main()
    {
        scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
        for(int i=1;i<=n;i++) insert(s[i]-'a');
        for(int i=1;i<=cnt;i++) c[l[i]]++;
        for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
        for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
        int k,p=1;
        scanf("%d%d",&t,&k);
        for(int i=cnt;i>=1;i--){
            if(t) siz[fa[a[i]]]+=siz[a[i]];
            else siz[a[i]]=1;
        }
        siz[1]=0;
        for(int i=cnt;i>=1;i--){
            sum[a[i]]=siz[a[i]];
            for(int j=0;j<26;j++)
                if(ch[a[i]][j]) sum[a[i]]+=sum[ch[a[i]][j]];
        }
        if(k>sum[1]){
            printf("-1
    ");
            return 0;
        }
        while(k){
            for(int i=0;i<26;i++){
                if(!ch[p][i]) continue;
                if(sum[ch[p][i]]>=k){
                    putchar(i+'a');
                    k-=siz[ch[p][i]];p=ch[p][i];break;
                }
                else k-=sum[ch[p][i]];
            }
        }
        putchar('
    ');
        return 0;
    }
    

    4、SP1811 LCS - Longest Common Substring

    题意:找两个串的最长公共子串

    类似 (kmp) 一样匹配两个串,失配的话一直向上跳

    (Code Below:)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=500000+10;
    int n,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],ans;
    char s[maxn];
    
    void insert(int c){
    	int p=last,q=++cnt;last=q;l[q]=l[p]+1;
    	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    	if(!p) fa[q]=1;
    	else {
    		int r=ch[p][c];
    		if(l[p]+1==l[r]) fa[q]=r;
    		else {
    			int s=++cnt;l[s]=l[p]+1;
    			memcpy(ch[s],ch[r],sizeof(ch[r]));
    			fa[s]=fa[r];fa[r]=fa[q]=s;
    			for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s; 
    		}
    	}
    }
    
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
    	for(int i=1;i<=n;i++) insert(s[i]-'a');
    	scanf("%s",s+1);n=strlen(s+1);
    	int p=1,c,len=0;
    	for(int i=1;i<=n;i++){
    		c=s[i]-'a';
    		if(ch[p][c]) len++,p=ch[p][c];
    		else {
    			for(;p&&!ch[p][c];p=fa[p]);
    			if(p) len=l[p]+1,p=ch[p][c];
    			else len=0,p=1;
    		}
    		ans=max(ans,len);
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    5、SP1812 LCS2 - Longest Common Substring II

    题意:找多个串的最长公共子串

    类似双串 (LCS) 一样,就是在匹配的时候记录下来长度,先取个 (min),再在 (min) 中取个 (max)

    (Code Below:)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=200000+10;
    int n,a[maxn],c[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],Max[maxn],Min[maxn],ans;
    char s[maxn];
    
    void insert(int c){
    	int p=last,q=++cnt;last=q;l[q]=l[p]+1;
    	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    	if(!p) fa[q]=1;
    	else {
    		int r=ch[p][c];
    		if(l[p]+1==l[r]) fa[q]=r;
    		else {
    			int s=++cnt;l[s]=l[p]+1;
    			memcpy(ch[s],ch[r],sizeof(ch[r]));
    			fa[s]=fa[r];fa[r]=fa[q]=s;
    			for(;ch[p][c]==r;p=fa[p]) ch[p][c]=s;
    		}
    	}
    }
    
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
    	for(int i=1;i<=n;i++) insert(s[i]-'a');
    	for(int i=1;i<=cnt;i++) c[l[i]]++;
    	for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
    	for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
    	for(int i=1;i<=cnt;i++) Min[i]=l[i];
    	int len,p,c;
    	while(scanf("%s",s+1)!=EOF){
    		len=0;p=1;n=strlen(s+1);
    		for(int i=1;i<=n;i++){
    			c=s[i]-'a';
    			if(ch[p][c]) p=ch[p][c],Max[p]=max(Max[p],++len);
    			else {
    				for(;p&&!ch[p][c];p=fa[p]);
    				if(p) len=l[p],p=ch[p][c],Max[p]=max(Max[p],++len);
    				else p=1,len=0;
    			}
    		}
    		for(int i=cnt;i>=1;i--){
    			Min[a[i]]=min(Min[a[i]],Max[a[i]]);
    			if(Max[a[i]]&&fa[a[i]]) Max[fa[a[i]]]=l[fa[a[i]]];
    			Max[a[i]]=0;
    		}
    	}
    	for(int i=2;i<=cnt;i++) ans=max(ans,Min[i]);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    6、[ZJOI2015]诸神眷顾的幻想乡

    题意:问多少条本质不同路径(可以翻转)

    边搜边建。因为叶子结点只有 (20) 个,每次就以度数为 (1) 的点开始搜索,建一个广义后缀自动机。

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=200000+10;
    int n,m,a[maxn],in[maxn],ch[maxn*20][26],fa[maxn*20],l[maxn*20],cnt;
    int head[maxn],to[maxn<<1],nxt[maxn<<1],tot;ll ans;
    
    inline int read(){
    	register int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    	return (f==1)?x:-x;
    }
    inline void add(int x,int y){
    	to[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    
    int insert(int c,int p){
    	int q=++cnt;l[q]=l[p]+1;
    	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    	if(!p) fa[q]=1;
    	else {
    		int r=ch[p][c];
    		if(l[p]+1==l[r]) fa[q]=r;
    		else {
    			int s=++cnt;l[s]=l[p]+1;
    			memcpy(ch[s],ch[r],sizeof(ch[r]));
    			fa[s]=fa[r];fa[r]=fa[q]=s;
    			for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
    		}
    	}
    	return q;
    }
    
    void dfs(int x,int f,int p){
    	p=insert(a[x],p);
    	for(int i=head[x],y;i;i=nxt[i]){
    		y=to[i];
    		if(y==f) continue;
    		dfs(y,x,p);
    	}
    }
    
    int main()
    {
    	n=read(),m=read();cnt=1;
    	for(int i=1;i<=n;i++) a[i]=read();
    	int x,y;
    	for(int i=1;i<n;i++){
    		x=read(),y=read();
    		add(x,y);add(y,x);
    		in[x]++;in[y]++;
    	}
    	for(int i=1;i<=n;i++)
    		if(in[i]==1) dfs(i,0,1);
    	for(int i=1;i<=cnt;i++)
    		ans+=l[i]-l[fa[i]];
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    7、[HEOI2016/TJOI2016]字符串

    题意:(q) 次询问,问在 ([a,b]) 的子串与 ([c,d]) 的最长前缀长度

    前缀不好处理,那么就干脆翻转一下好了。

    最长前缀不好处理,那么就干脆二分答案一下好了。

    二分后转化为存在性问题,那么就干脆套个线段树合并好了。

    建一个后缀自动机,(l) 的长度就倍增一下。

    时间复杂度 (O(nlog^2 n))

    (Code Below:)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=500000+10;
    int n,m,a[maxn],b[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn];
    int pos[maxn],f[maxn][21],T[maxn],L[maxn*60],R[maxn*60],sum[maxn*60],tot;
    char s[maxn];
    
    inline int read(){
    	register int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    	return (f==1)?x:-x;
    }
    
    void insert(int c){
    	int p=last,q=++cnt;last=q;l[q]=l[p]+1;
    	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    	if(!p) fa[q]=1;
    	else {
    		int r=ch[p][c];
    		if(l[p]+1==l[r]) fa[q]=r;
    		else {
    			int s=++cnt;l[s]=l[p]+1;
    			memcpy(ch[s],ch[r],sizeof(ch[r]));
    			fa[s]=fa[r];fa[r]=fa[q]=s;
    			for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
    		}
    	}
    }
    
    void pushup(int now){
    	sum[now]=sum[L[now]]+sum[R[now]];
    }
    
    void update(int &now,int l,int r,int x){
    	if(!now) now=++tot;
    	if(l == r){sum[now]++;return ;}
    	int mid=(l+r)>>1;
    	if(x <= mid) update(L[now],l,mid,x);
    	else update(R[now],mid+1,r,x);
    	pushup(now);
    }
    
    int merge(int x,int y,int l,int r){
    	if(!x||!y) return x+y;
    	if(l == r){sum[x]+=sum[y];return x;}
    	int mid=(l+r)>>1,z=++tot;
    	L[z]=merge(L[x],L[y],l,mid);
    	R[z]=merge(R[x],R[y],mid+1,r);
    	pushup(z);
    	return z;
    }
    
    int query(int now,int Le,int Ri,int l,int r){
    	if(!now) return 0;
    	if(Le <= l && r <= Ri) return sum[now];
    	int mid=(l+r)>>1,ans=0;
    	if(Le <= mid) ans+=query(L[now],Le,Ri,l,mid);
    	if(Ri > mid) ans+=query(R[now],Le,Ri,mid+1,r);
    	return ans;
    }
    
    int check(int len,int x,int L,int R){
    	for(int i=20;i>=0;i--)
    		if(f[x][i]&&l[f[x][i]]>=len) x=f[x][i];
    	return query(T[x],L+len-1,R,1,n);
    }
    
    int main()
    {
    	n=read(),m=read();
    	scanf("%s",s+1);last=cnt=1;
    	reverse(s+1,s+n+1);
    	for(int i=1;i<=n;i++){
    		insert(s[i]-'a');pos[i]=last;
    		update(T[pos[i]],1,n,i);
    	}
    	for(int i=1;i<=cnt;i++) b[l[i]]++;
    	for(int i=1;i<=cnt;i++) b[i]+=b[i-1];
    	for(int i=1;i<=cnt;i++) a[b[l[i]]--]=i;
    	for(int i=cnt;i>=1;i--)
    		if(fa[a[i]]) T[fa[a[i]]]=merge(T[fa[a[i]]],T[a[i]],1,n);
    	for(int i=1;i<=cnt;i++) f[i][0]=fa[i];
    	for(int j=1;j<=20;j++)
    		for(int i=1;i<=cnt;i++) f[i][j]=f[f[i][j-1]][j-1];
    	int a,b,c,d,l,r,mid,ans;
    	while(m--){
    		a=n-read()+1,b=n-read()+1,c=n-read()+1,d=n-read()+1;
    		l=0,r=min(a-b+1,c-d+1),ans=0;
    		while(l<=r){
    			mid=(l+r)>>1;
    			if(check(mid,pos[c],b,a)) l=mid+1,ans=mid;
    			else r=mid-1;
    		}
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    8、[HAOI2016]找相同字符

    题意:问两个串的公共子串个数

    如核心代码 ((l[i]-l[fa[i]]) imes siz[i])

    	for(int i=2;i<=cnt;i++) sum[a[i]]=sum[fa[a[i]]]+(ll)(l[a[i]]-l[fa[a[i]]])*siz[a[i]];
    	scanf("%s",s+1);n=strlen(s+1);
    	int len=0,p=1,c;
    	for(int i=1;i<=cnt;i++){
    		c=s[i]-'a';
    		for(;p&&!ch[p][c];p=fa[p]);
    		if(!p) p=1,len=0;
    		else {
    			len=min(len,l[p])+1;p=ch[p][c];
    			ans+=sum[fa[p]]+(ll)(len-l[fa[p]])*siz[p];
    		}
    	}
    

    (Code Below:)

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=400000+10;
    int n,a[maxn],b[maxn],last,cnt,ch[maxn][26],fa[maxn],l[maxn],siz[maxn];
    char s[maxn];ll sum[maxn],ans;
    
    void insert(int c){
    	int p=last,q=++cnt;last=q;l[q]=l[p]+1;
    	for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=q;
    	if(!p) fa[q]=1;
    	else {
    		int r=ch[p][c];
    		if(l[p]+1==l[r]) fa[q]=r;
    		else {
    			int s=++cnt;l[s]=l[p]+1;
    			memcpy(ch[s],ch[r],sizeof(ch[r]));
    			fa[s]=fa[r];fa[r]=fa[q]=s;
    			for(;p&&ch[p][c]==r;p=fa[p]) ch[p][c]=s;
    		}
    	}
    	siz[q]=1;
    }
    
    int main()
    {
    	scanf("%s",s+1);n=strlen(s+1);last=cnt=1;
    	for(int i=1;i<=n;i++) insert(s[i]-'a');
    	for(int i=1;i<=cnt;i++) b[l[i]]++;
    	for(int i=1;i<=cnt;i++) b[i]+=b[i-1];
    	for(int i=cnt;i>=1;i--) a[b[l[i]]--]=i;
    	for(int i=cnt;i>=1;i--) siz[fa[a[i]]]+=siz[a[i]];
    	for(int i=2;i<=cnt;i++) sum[a[i]]=sum[fa[a[i]]]+(ll)(l[a[i]]-l[fa[a[i]]])*siz[a[i]];
    	scanf("%s",s+1);n=strlen(s+1);
    	int len=0,p=1,c;
    	for(int i=1;i<=cnt;i++){
    		c=s[i]-'a';
    		for(;p&&!ch[p][c];p=fa[p]);
    		if(!p) p=1,len=0;
    		else {
    			len=min(len,l[p])+1;p=ch[p][c];
    			ans+=sum[fa[p]]+(ll)(len-l[fa[p]])*siz[p];
    		}
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    053592
    053591
    053590
    053589
    053588
    053676
    C# WPF Border控件总结
    Android Studio 添加jar或aar依赖的两种方式
    javascript Date与string之间的转换
    C#:使用dsoframer.ocx控件实现内嵌office效果(详解)
  • 原文地址:https://www.cnblogs.com/owencodeisking/p/10072748.html
Copyright © 2011-2022 走看看