zoukankan      html  css  js  c++  java
  • [学习笔记]后缀排序

    后缀排序学多了以后就只会前缀排序了(输出1-n的整数)

    【模板】后缀排序

    存个板子 倍增

    (Code Below:)

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=1000000+10;
    int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn];
    char a[maxn];
    
    void SA(){
    	int i,k,p,f=0;m=128;
    	for(i=1;i<=n;i++) rnk[i]=a[i];
    	for(i=1;i<=m;i++) tax[i]=0;
    	for(i=1;i<=n;i++) tax[rnk[i]]++;
    	for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    	for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
    	for(k=1,p=0;p<n;m=p,k<<=1){
    		p=0;
    		for(i=n-k+1;i<=n;i++) tp[++p]=i;
    		for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
    		for(i=1;i<=m;i++) tax[i]=0;
    		for(i=1;i<=n;i++) tax[rnk[i]]++;
    		for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    		for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
    		swap(rnk,tp);rnk[sa[1]]=p=1;
    		for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
    	}
    	for(i=1;i<=n;i++) rnk[sa[i]]=i;
    	for(i=1;i<=n;i++){
    		p=rnk[sa[i]-1];if(f) f--;
    		while(a[i+f]==a[p+f]) f++;
    		h[rnk[i]]=f;
    	}
    }
    
    signed main()
    {
    	scanf("%s",a+1);n=strlen(a+1);
    	SA();
    	for(int i=1;i<=n;i++) printf("%d ",sa[i]);printf("
    ");
    	return 0;
    }
    

    1、SP694 DISUBSTR - Distinct Substrings

    题意:问本质不同的子串数量

    其实就是减去相邻后缀的 (LCP),即 (height) 数组

    (Code Below:)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=1000+10;
    int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],ans;
    char a[maxn];
    
    void SA(){
    	memset(sa,0,sizeof(sa));
    	memset(tax,0,sizeof(tax));
    	memset(rnk,0,sizeof(rnk));
    	memset(tp,0,sizeof(tp));
    	int i,k,p,f=0;m=128;
    	for(i=1;i<=n;i++) rnk[i]=a[i];
    	for(i=1;i<=m;i++) tax[i]=0;
    	for(i=1;i<=n;i++) tax[rnk[i]]++;
    	for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    	for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
    	for(k=1,p=1;p<n;m=p,k<<=1){
    		p=0;
    		for(i=n-k+1;i<=n;i++) tp[++p]=i;
    		for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k; 
    		for(i=1;i<=m;i++) tax[i]=0;
    		for(i=1;i<=n;i++) tax[rnk[i]]++;
    		for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    		for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
    		swap(rnk,tp);rnk[sa[1]]=p=1;
    		for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
    	}
    	for(int i=1;i<=n;i++) rnk[sa[i]]=i;
    	for(int i=1;i<=n;i++){
    		p=sa[rnk[i]-1];
    		if(f) f--;
    		while(a[i+f]==a[p+f]) f++;
    		h[rnk[i]]=f;
    	}
    }
    
    int main()
    {
    	int T;
    	scanf("%d",&T);
    	while(T--){
    		scanf("%s",a+1);n=strlen(a+1);
    		ans=n*(n+1)/2;SA();
    		for(int i=1;i<=n;i++) ans-=h[i];
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    

    (SP705) 就是数据范围开大一点,(ans) 开个 (long long)

    2、[USACO5.1]乐曲主题Musical Themes

    题意:求最长重复 (2) 次不重叠子串

    正解 (O(n^2)),但是我们可以用后缀数组+二分优化到 (O(nlog n))

    处理出 (height) 数组后二分长度,如果 (height<k) 就重置,否则处理出连续一段区间的 (minsa)(maxsa),若 (maxsa-minsa>k) 就说明有两个子串

    (Code Below:)

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=50000+10;
    int n,m,a[maxn],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],ans;
    
    void SA(){
    	int i,k,p,f=0;m=2000;
    	for(i=1;i<=n;i++) rnk[i]=a[i];
    	for(i=1;i<=m;i++) tax[i]=0;
    	for(i=1;i<=n;i++) tax[rnk[i]]++;
    	for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    	for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
    	for(k=1,p=1;p<n;m=p,k<<=1){
    		p=0;
    		for(i=n-k+1;i<=n;i++) tp[++p]=i;
    		for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k; 
    		for(i=1;i<=m;i++) tax[i]=0;
    		for(i=1;i<=n;i++) tax[rnk[i]]++;
    		for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    		for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
    		swap(rnk,tp);rnk[sa[1]]=p=1;
    		for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
    	}
    	for(int i=1;i<=n;i++) rnk[sa[i]]=i;
    	for(int i=1;i<=n;i++){
    		p=sa[rnk[i]-1];if(f) f--;
    		while(a[i+f]==a[p+f]) f++;
    		h[rnk[i]]=f;
    	}
    }
    
    int check(int k){
    	int l=sa[1],r=sa[1];
    	for(int i=2;i<=n;i++){
    		if(h[i]<k) l=r=sa[i];
    		else {
    			l=min(l,sa[i]);
    			r=max(r,sa[i]);
    			if(r-l>k) return 1;
    		}
    	}
    	return 0;
    }
    
    int main()
    {
    	scanf("%d",&n);
    	if(n<10){
    		printf("0
    ");
    		return 0;
    	}
    	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]+1000;
    	a[n]=0;SA();
    	int l=3,r=n/2,mid;
    	while(l<r){
    		mid=(l+r+1)>>1;
    		if(check(mid)) l=mid;
    		else r=mid-1;
    	}
    	l++;
    	printf("%d
    ",(l>=5)?l:0);
    	return 0;
    }
    

    3、SP687 REPEATS - Repeats

    题意:求重复次数最多的连续重叠子串

    (height) 数组上瞎搞

    (Code Below:)

    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <iostream>
    using namespace std;
    const int maxn=100000+10;
    int n,m,RMQ[maxn][18],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn];
    char a[maxn];
    
    void SA(){
    	int i,k,p,f=0;m=128;
    	for(i=1;i<=n;i++) rnk[i]=a[i];
    	for(i=1;i<=m;i++) tax[i]=0;
    	for(i=1;i<=n;i++) tax[rnk[i]]++;
    	for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    	for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
    	for(k=1,p=1;p<n;m=p,k<<=1){
    		p=0;
    		for(i=n-k+1;i<=n;i++) tp[++p]=i;
    		for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k; 
    		for(i=1;i<=m;i++) tax[i]=0;
    		for(i=1;i<=n;i++) tax[rnk[i]]++;
    		for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    		for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
    		swap(rnk,tp);rnk[sa[1]]=p=1;
    		for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
    	}
    	for(int i=1;i<=n;i++) rnk[sa[i]]=i;
    	for(int i=1;i<=n;i++){
    		p=sa[rnk[i]-1];if(f) f--;
    		while(a[i+f]==a[p+f]) f++;
    		h[rnk[i]]=f;
    	}
    	for(int i=1;i<=n;i++) RMQ[i][0]=i;
    	for(int j=1;j<18;j++)
    		for(int i=1;i+(1<<j)-1<=n;i++)
    			RMQ[i][j]=h[RMQ[i][j-1]]<h[RMQ[i+(1<<(j-1))][j-1]]?RMQ[i][j-1]:RMQ[i+(1<<(j-1))][j-1];
    }
    
    int query(int l,int r){
    	int k=log2(r-l+1);
    	return h[RMQ[l][k]]<h[RMQ[r-(1<<k)+1][k]]?RMQ[l][k]:RMQ[r-(1<<k)+1][k];
    }
    
    int LCP(int a,int b){
    	int l=rnk[a],r=rnk[b];
    	if(l>r) swap(l,r);
    	return h[query(l+1,r)];
    }
    
    int main()
    {
    	int T;
    	scanf("%d",&T);
    	while(T--){
    		#define mem(x) memset(x,0,sizeof(x))
    		mem(a);mem(sa);mem(tax);mem(rnk);mem(tp);mem(h);
    		scanf("%d",&n);
    		for(int i=1;i<=n;i++){
    			a[i]=getchar();
    			while(!isalpha(a[i])) a[i]=getchar();	
    		}
    		SA();
    		int k,t,now=0,ans=0;
    		for(int i=1;i<n;i++){
    			for(int j=1;j<=n;j+=i){
    				k=LCP(j,j+i);
    				now=k/i;t=j-(i-k%i);
    				if(now>=0&&LCP(t,t+i)>=i-k%i) now++;
    				ans=max(ans,now);
    			}
    		}
    		printf("%d
    ",ans+1);
    	}
    	return 0;
    }
    

    4、[AHOI2013]差异

    题意:求 (frac{(n-1)n(n+1)}{2}-2 imes sum_{1leq i<jleq n}LCP(i,j))

    将问题转化为所有区间 (height) 最小值之和,然后正着一遍单调栈,反着一遍单调栈,算一遍每个数的贡献

    (Code Below:)

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=500000+10;
    int n,m,sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],L[maxn],R[maxn],sta[maxn],val[maxn],top;
    char a[maxn];ll ans;
    
    void SA(){
        int i,k,p,f=0;m=128;
        for(i=1;i<=n;i++) rnk[i]=a[i];
        for(i=1;i<=m;i++) tax[i]=0;
        for(i=1;i<=n;i++) tax[rnk[i]]++;
        for(i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
        for(k=1,p=0;p<n;m=p,k<<=1){
            p=0;
            for(i=n-k+1;i<=n;i++) tp[++p]=i;
            for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            for(i=1;i<=m;i++) tax[i]=0;
            for(i=1;i<=n;i++) tax[rnk[i]]++;
            for(i=1;i<=m;i++) tax[i]+=tax[i-1];
            for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
            swap(rnk,tp);rnk[sa[1]]=p=1;
            for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p;
        }
        for(i=1;i<=n;i++) rnk[sa[i]]=i;
        for(i=1;i<=n;i++){
            p=sa[rnk[i]-1];if(f) f--;
            while(a[i+f]==a[p+f]) f++;
            h[rnk[i]]=f;
        }
    }
    
    signed main()
    {
        scanf("%s",a+1);n=strlen(a+1);
        SA();ans=(ll)(n-1)*n*(n+1)/2;
        for(int i=2;i<=n;i++){
            L[i]=1;
            while(top&&sta[top]>=h[i]) L[i]+=val[top],top--;
            sta[++top]=h[i];val[top]=L[i];
        }
        top=0;
        for(int i=n;i>=2;i--){
            R[i]=1;
            while(top&&sta[top]>h[i]) R[i]+=val[top],top--;
            sta[++top]=h[i];val[top]=R[i];
        }
        for(int i=2;i<=n;i++) ans-=2ll*L[i]*R[i]*h[i];
        printf("%lld
    ",ans);
        return 0;
    }
    

    5、[SDOI2016]生成魔咒

    题意:求每一个 (i),字符串 (1-i) 中本质不同串的个数

    因为字符集很大,考虑用后缀数组

    我们们后面添加一个字符换成删除一个字符,那么就是在双向链表上删除两个 (height)(max),保留两个 (height)(min),反着来一下

    (Code Below:)

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const int maxn=100000+10;
    int n,m,a[maxn],mp[maxn],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],pre[maxn],nxt[maxn];ll ans[maxn];
    
    void SA(){
    	int i,k,p,f=0;m=n;
    	for(i=1;i<=n;i++) rnk[i]=a[i];
    	for(i=1;i<=m;i++) tax[i]=0;
    	for(i=1;i<=n;i++) tax[rnk[i]]++;
    	for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    	for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
    	for(k=1,p=0;p<n;m=p,k<<=1){
    		p=0;
    		for(i=n-k+1;i<=n;i++) tp[++p]=i;
    		for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
    		for(i=1;i<=m;i++) tax[i]=0;
    		for(i=1;i<=n;i++) tax[rnk[i]]++;
    		for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    		for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
    		swap(rnk,tp);rnk[sa[1]]=p=1;
    		for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p; 
    	}
    	for(i=1;i<=n;i++) rnk[sa[i]]=i;
    	for(i=1;i<=n;i++){
    		p=sa[rnk[i]-1];if(f) f--;
    		while(a[i+f]==a[p+f]) f++;
    		h[rnk[i]]=f;
    	}
    }
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++){
        	scanf("%d",&a[i]);
        	mp[i]=a[i];
    	}
    	sort(mp+1,mp+n+1);
    	int cnt=unique(mp+1,mp+n+1)-mp-1;
    	for(int i=1;i<=n;i++)
    		a[i]=lower_bound(mp+1,mp+cnt+1,a[i])-mp;
    	reverse(a+1,a+n+1);
    	SA();
    	for(int i=1;i<=n;i++) pre[i]=i-1,nxt[i]=i+1;
    	for(int i=1;i<=n;i++){
    		ans[i]=(ll)(n-i+1-max(h[rnk[i]],h[nxt[rnk[i]]]));
    		h[nxt[rnk[i]]]=min(h[nxt[rnk[i]]],h[rnk[i]]);
    		h[rnk[i]]=0;
    		if(rnk[i]) pre[nxt[rnk[i]]]=pre[rnk[i]];
    		nxt[pre[rnk[i]]]=nxt[rnk[i]];
    	}
    	for(int i=n;i>=1;i--) ans[i]+=ans[i+1];
    	for(int i=n;i>=1;i--) printf("%lld
    ",ans[i]);
        return 0;
    }
    

    6、[NOI2015]品酒大会

    后缀数组好题!

    因为 (r) 相似是 (r-1) 相似但 (r-1) 相似不是 (r) 相似,我们考虑现将 (height) 从大到小排序,然后用并查集维护一下。因为 (a_p imes a_q) 取最大时 (a_p)(a_q) 可能为负数,那么我们记录一个最大值和最小值,每次相乘一下,更新答案。最终要求的方案数是 (num) 的后缀和,(a_p imes a_q) 最大时为 (ans) 的后缀最大值

    (Code Below:)

    #include <bits/stdc++.h>
    #define int long long
    #define ll long long
    using namespace std;
    const int maxn=600000+10;
    int n,m,b[maxn],val[maxn],sa[maxn],tax[maxn],rnk[maxn],tp[maxn],h[maxn],fa[maxn],siz[maxn],Max[maxn],Min[maxn];ll num[maxn],ans[maxn];
    char a[maxn];
    
    void SA(){
    	int i,k,p,f=0;m=128;
    	for(i=1;i<=n;i++) rnk[i]=a[i];
    	for(i=1;i<=m;i++) tax[i]=0;
    	for(i=1;i<=n;i++) tax[rnk[i]]++;
    	for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    	for(i=n;i>=1;i--) sa[tax[rnk[i]]--]=i;
    	for(k=1,p=0;p<n;m=p,k<<=1){
    		p=0;
    		for(i=n-k+1;i<=n;i++) tp[++p]=i;
    		for(i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
    		for(i=1;i<=m;i++) tax[i]=0;
    		for(i=1;i<=n;i++) tax[rnk[i]]++;
    		for(i=1;i<=m;i++) tax[i]+=tax[i-1];
    		for(i=n;i>=1;i--) sa[tax[rnk[tp[i]]]--]=tp[i];
    		swap(rnk,tp);rnk[sa[1]]=p=1;
    		for(i=2;i<=n;i++) rnk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+k]==tp[sa[i]+k])?p:++p; 
    	}
    	for(i=1;i<=n;i++) rnk[sa[i]]=i;
    	for(i=1;i<=n;i++){
    		p=sa[rnk[i]-1];if(f) f--;
    		while(a[i+f]==a[p+f]) f++;
    		h[rnk[i]]=f;
    	}
    }
    
    int find(int x){
    	return (x==fa[x])?x:fa[x]=find(fa[x]);
    }
    
    void merge(int x,int y){
    	siz[y]+=siz[x];
    	Max[y]=max(Max[y],Max[x]);
    	Min[y]=min(Min[y],Min[x]);
    	fa[x]=y;
    }
    
    bool cmp(int x,int y){
    	return h[x]>h[y];
    }
    
    signed main()
    {
        scanf("%lld",&n);
        scanf("%s",a+1);
        for(int i=1;i<=n;i++){
        	scanf("%lld",&val[i]);
        	fa[i]=i;siz[i]=1;
    	}
    	for(int i=0;i<n;i++) ans[i]=-1e18;
    	SA();
    	for(int i=1;i<=n;i++) Max[rnk[i]]=Min[rnk[i]]=val[i];
    	for(int i=1;i<n;i++) b[i]=i+1;
    	sort(b+1,b+n,cmp);
    	int x,y;
    	for(int i=1;i<n;i++){
    		x=find(b[i]),y=find(b[i]-1);
    		num[h[x]]+=(ll)siz[x]*siz[y];
    		ans[h[x]]=max(ans[h[x]],(ll)Max[x]*Max[y]);
    		ans[h[x]]=max(ans[h[x]],(ll)Max[x]*Min[y]);
    		ans[h[x]]=max(ans[h[x]],(ll)Min[x]*Max[y]);
    		ans[h[x]]=max(ans[h[x]],(ll)Min[x]*Min[y]);
    		merge(x,y);
    	}
    	for(int i=n-2;i>=0;i--) num[i]+=num[i+1],ans[i]=max(ans[i],ans[i+1]);
    	for(int i=0;i<n;i++) printf("%lld %lld
    ",num[i],(ans[i]==-1e18)?0:ans[i]);
        return 0;
    }
    
  • 相关阅读:
    python基础(str,list,tuple)
    MySQL数据类型概念
    Ubuntu安装sublime
    Ubuntu安装pycharm
    ubuntu安装mysql
    微信小程序注册开发流程
    新开篇
    被玩坏了的题——马的遍历
    一道数学恶心题——小凯的疑惑
    搜索基础题:八皇后
  • 原文地址:https://www.cnblogs.com/owencodeisking/p/10104431.html
Copyright © 2011-2022 走看看