[学习笔记]后缀排序

后缀排序学多了以后就只会前缀排序了(输出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("\n");
	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\n",ans);
	}
	return 0;
}

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

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

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

正解 \(O(n^2)\),但是我们可以用后缀数组+二分优化到 \(O(n\log 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\n");
		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\n",(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\n",ans+1);
	}
	return 0;
}

4、[AHOI2013]差异

题意:求 \(\frac{(n-1)n(n+1)}{2}-2\times \sum_{1\leq i<j\leq 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\n",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\n",ans[i]);
    return 0;
}

6、[NOI2015]品酒大会

后缀数组好题!

因为 \(r\) 相似是 \(r-1\) 相似但 \(r-1\) 相似不是 \(r\) 相似,我们考虑现将 \(height\) 从大到小排序,然后用并查集维护一下。因为 \(a_p\times a_q\) 取最大时 \(a_p\)\(a_q\) 可能为负数,那么我们记录一个最大值和最小值,每次相乘一下,更新答案。最终要求的方案数是 \(num\) 的后缀和,\(a_p\times 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\n",num[i],(ans[i]==-1e18)?0:ans[i]);
    return 0;
}
posted @ 2018-12-11 19:20  Owen_codeisking  阅读(293)  评论(0编辑  收藏  举报