后缀数组水水水水水水题

首先:

jxrjxrjxr Orz,没有您我们都会死~

然后就是我从jxr神犇那里借鉴(照抄)过来的后缀数组模板。

#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=100000+1000;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[y[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];
}
void getsa(int m){
	for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
	radix(m);
	for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
		for(int i=n-k+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
		radix(m);swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
		if(p==n)return;
	}
}
void geth(){
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	memset(h,0,sizeof(h));
	for(int i=1,k=0;i<=n;h[rk[i++]]=k)
	for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
真是十分的  短  啊~~~~~~

话说我现在才会后缀数组真的好吗,简直违反基本法啊。

不过还是稀里糊涂地看懂(背下来)了。

于是就可以开心地去水题了。

没错其实我就是把09年那篇论文里面的题给水了下。

然后来写下一句话题解。

POJ1743:不可重叠最长重复子串

二分答案,height分组,维护最大最小sa,判定一下。

<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=20000+5;
int s[N],a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
int abs(int x){
	return max(x,-x);
}
void radix(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[y[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];
}
void getsa(int m){
	for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
	radix(m);
	for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
		for(int i=n-k+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
		radix(m);swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
		if(p==n)return ;
	}
}
void geth(){
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	memset(h,0,sizeof(h));
	for(int i=1,k=0;i<=n;h[rk[i++]]=k)
	for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
bool check(int k){
	int _min=sa[1],_max=sa[1];
	for(int i=2;i<=n+1;i++){
		if(i==n+1||h[i]<k){
			if(_max-_min>=k)return true;
			_max=_min=sa[i];
		}else{
			_min=min(_min,sa[i]);
			_max=max(_max,sa[i]);
		}
	}
	return false;
}
int main(){
	while(scanf("%d",&n)&&n){
		for(int i=1;i<=n;i++)scanf("%d",&s[i]);
		for(int i=1;i<n;i++)s[i]=s[i+1]-s[i]+100;
		n--;
		x=a;y=b;
		getsa(200);geth();
		int l=0,r=n/2;
		while(l<=r){
			int mid=l+r>>1;
			if(check(mid))l=mid+1;
			else r=mid-1;
		}
		if(l>=5)printf("%d\n",l);
		else puts("0");
	}
	return 0;
}</span>

SPOJ 694&705:本质不同的子串个数

sigma(n-sa[i]+1-height[i])

<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=1000+5;
char s[N];
int sa[N],h[N],rk[N],a[N],b[N],c[N],*x,*y,n;
void radix(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[y[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];
}
void getsa(int m){
	for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
	radix(m);
	for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
		for(int i=n-k+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
		radix(m);swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
		if(p==n)return;
	}
}
void geth(){
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	memset(h,0,sizeof(h));
	for(int i=1,k=0;i<=n;h[rk[i++]]=k)
	for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int main(){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%s",s+1);
		n=strlen(s+1);
		x=a;y=b;
		getsa(200);geth();
		int ans=0;
		for(int i=1;i<=n;i++)
		ans+=n-sa[i]+1-h[i];
		printf("%d\n",ans);
	}
	return 0;
}</span>


POJ 3693:重复次数最多的连续重复子串(好绕啊)

枚举长度l,计算次数,复杂度是n*调和级数(n),中间细节比较麻烦。。。。。

<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=100000+10;
struct Suffix_Array{
	char s[N];
	int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
	int st[N][20];
	void init(char *t){
		n=strlen(t+1);
		for(int i=1;i<=n;i++)s[i]=t[i];
		x=a;y=b;
	}
	void radix(int m){
		for(int i=1;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[x[y[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];
	}
	void getsa(int m){
		for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
		radix(m);
		for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
			for(int i=n-k+1;i<=n;i++)y[++p]=i;
			for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
			radix(m);swap(x,y);x[sa[1]]=p=1;
			for(int i=2;i<=n;i++)
			x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
			if(p==n)return;
		}
	}
	void geth(){
		for(int i=1;i<=n;i++)rk[sa[i]]=i;
		memset(h,0,sizeof(h));
		for(int i=1,k=0;i<=n;h[rk[i++]]=k)
		for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
	}
	void rmq_init(){
		for(int i=1;i<=n;i++)st[i][0]=h[i];
		for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i+(1<<j)-1<=n;i++)
		st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	}
	int rmq(int l,int r){
		int k=log2(r-l+1);
		return min(st[l][k],st[l+(1<<k)-1][k]);
	}
	int query(int i,int j){
		int l=rk[i],r=rk[j];
		if(l>r)swap(l,r);
		return rmq(l+1,r);
	}
	void pre(){
		getsa(200);geth();rmq_init();
	}
}sol;
char s[N];
int a[N];
int main(){
	int kase=0;
	while(true){
		scanf("%s",s+1);
		if(s[1]=='#')break;
		int n=strlen(s+1);
		sol.init(s);sol.pre();
		int ans=0,top=0;
		for(int l=1;l<=n;l++)
		for(int i=1;i+l<=n;i+=l){
			int r=sol.query(i,i+l);
			int ti=r/l+1;
			int k=i-(l-r%l);
			if(k>=1&&r%l)
			if(sol.query(k,k+l)>=r)ti++;
			if(ti>ans){
				ans=ti;
				a[top=1]=l;
			}else if(ti==ans)a[++top]=l;
		}
		int len=-1,st;
		for(int i=1;i<=n&&len==-1;i++)
		for(int j=1;j<=top;j++){
			int l=a[j];
			if(sol.query(sol.sa[i],sol.sa[i]+l)>=(ans-1)*l){
				len=l;
				st=sol.sa[i];
				break;
			}
		}
		printf("Case %d: ",++kase);
		for(int i=st,j=1;j<=len*ans;j++,i++)putchar(s[i]);
		putchar('\n');
	}
	return 0;
}</span>


POJ 2774:双串最长公共子串

两个串中间加个奇怪的符号然后连接起来,跑一遍后缀数组,当sa[i]和sa[i-1]不在一个串时的最大height[i]即为答案。

<span style="font-size:14px;">#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=200000+10;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[y[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];
}
void getsa(int m){
	for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
	radix(m);
	for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
		for(int i=n-k+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
		radix(m);swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
		if(p==n)break;
	}
}
void geth(){
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	memset(h,0,sizeof(h));
	for(int i=1,k=0;i<=n;h[rk[i++]]=k)
	for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int main(){
	scanf("%s",s+1);
	int m=strlen(s+1);
	s[m+1]='$';
	scanf("%s",s+m+2);
	n=strlen(s+1);
	x=a;y=b;
	getsa(200);geth();
	int ans=0;
	for(int i=2;i<=n;i++)
	if(sa[i-1]<=m^sa[i]<=m)ans=max(ans,h[i]);
	printf("%d",ans);
	return 0;
}</span>

POJ 3415:长度不小于k的公共子串个数

继续串接,跑一遍后缀数组,对于每一个A的后缀和B的后缀计算一次LCP算出答案,于是N^2爆炸,转用单调栈维护A的height,扫到B的时候算一下,反过来再做一次,得出总答案。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
const int N=200000+5;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[y[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];
}
void getsa(int m){
	for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
	radix(m);
	for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
		for(int i=n-k+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
		radix(m);swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
		if(p==n)return;
	}
}
void geth(){
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	memset(h,0,sizeof(h));
	for(int i=1,k=0;i<=n;h[rk[i++]]=k)
	for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int st[N][2];
int main(){
	int k;
	while(scanf("%d",&k)&&k){
		scanf("%s",s+1);
		int m=strlen(s+1);
		s[++m]='$';
		scanf("%s",s+m+1);
		n=strlen(s+1);
		x=a;y=b;
		getsa(200);geth();
		ll sum=0,tot=0,top=0;
		for(int i=2;i<=n+1;i++){
			if(h[i]<k)tot=top=0;
			else{
				int cnt=0;
				if(sa[i-1]<m)cnt++,tot+=h[i]-k+1;
				while(top&&h[i]<=st[top-1][0]){
					top--;
					tot-=st[top][1]*(st[top][0]-h[i]);
					cnt+=st[top][1];
				}
				st[top][0]=h[i];st[top++][1]=cnt;
				if(sa[i]>m)sum+=tot;
			}
		}
		tot=top=0;
		for(int i=2;i<=n+1;i++){
			if(h[i]<k)tot=top=0;
			else{
				int cnt=0;
				if(sa[i-1]>m)cnt++,tot+=h[i]-k+1;
				while(top>0&&h[i]<=st[top-1][0]){
					top--;
					tot-=st[top][1]*(st[top][0]-h[i]);
					cnt+=st[top][1];
				}
				st[top][0]=h[i];st[top++][1]=cnt;
				if(sa[i]<m)sum+=tot;
			}
		}
		printf("%lld\n",sum);
	}
	return 0;
}

POJ 3294:出现在不少于k个字符串中的最长子串

这题其实可以KMP做我会乱说= =+(常数我吃了)。

n个串连起来,中间有奇怪的不同的字符,跑一遍后缀数组,二分答案,height分组,每组判定一下有木有集齐(7个后缀召唤神龙?)k个不在同一个串的后缀,集齐了就可以(召唤神龙)return true

顺便说这题召唤出了我人生中的第一个OLE(我也不造怎么回事啊,然后莫名其妙就A了)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
const int N=100000+1000;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],*x,*y,n;
void radix(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[y[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];
}
void getsa(int m){
	for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
	radix(m);
	for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
		for(int i=n-k+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
		radix(m);swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
		if(p==n)return;
	}
}
void geth(){
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	memset(h,0,sizeof(h));
	for(int i=1,k=0;i<=n;h[rk[i++]]=k)
	for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int in[N],T,k,now[105];
bool check(int len){
	int tot=0;
	T=1;
	memset(now,0,sizeof(now));
	for(int i=1;i<=n;i++){
		if(h[i]<len){
			if(tot>k)return true;
			tot=1;
			now[in[sa[i]]]=++T;
		}
		else{
			if(now[in[sa[i]]]!=T){
				now[in[sa[i]]]=T;
				tot++;
			}
		}
	}
	if(tot>k)return true;
	return false;
}
int ans[N];
void print(int len){
	int tot=0,sz=0;
	T=1;
	memset(now,0,sizeof(now));
	for(int i=1;i<=n;i++){
		if(h[i]<len){
			if(tot>k)ans[++sz]=sa[i-1];
			tot=1;
			now[in[sa[i]]]=++T;
		}
		else{
			if(now[in[sa[i]]]!=T){
				now[in[sa[i]]]=T;
				tot++;
			}
		}
	}
	if(tot>k)ans[++sz]=sa[n];
	ans[0]=sz;
}
int main(){
	int m;bool flag=false;
	while(scanf("%d",&m)&&m){
		memset(s,0,sizeof(s));
		int tmp=1,top=1;
		int l=0,r=1005;
		for(int i=1;i<=m;i++){
			scanf("%s",s+tmp);
			int len=strlen(s+tmp);
			r=min(r,len);
			for(int j=tmp;j<tmp+len;j++)
			in[j]=i;
			s[tmp+=len]=top++;
			tmp++;
		}
		n=strlen(s+1);
		x=a;y=b;
		getsa(300);geth();
		k=m/2;
		while(l<=r){
			int mid=l+r>>1;
			if(check(mid))l=mid+1;
			else r=mid-1;
		}
		if(flag)puts("");
		flag=true;
		if(l==1)puts("?");
		else{
			print(l-1); 
			for(int i=1;i<=ans[0];i++){
				for(int j=ans[i];j<ans[i]+l-1;j++)
				putchar(s[j]);
				putchar('\n');
			}
		}
	}
	return 0;
}

SPOJ 220:每个字符至少出现两次且不重叠的最长子串

其实。。。。。这题跟上题差不多,那个至少出现两次并无卵用,因为判断的时候只要串长不为0,出现1次的都干掉了。

串接,二分答案,height分组(TM还能有点别的?)然后维护mx和mn,即最大最小sa值(跟第1题的不可重叠一样的)。

#include<iostream>
#include<cstdio>
#include<cstring>
#define cmp(x) (y[sa[i]+x]==y[sa[i-1]+x])
using namespace std;
const int N=100000+1000;
char s[N];
int a[N],b[N],c[N],sa[N],rk[N],h[N],n,*x,*y;
void radix(int m){
	for(int i=1;i<=m;i++)c[i]=0;
	for(int i=1;i<=n;i++)c[x[y[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];
}
void getsa(int m){
	for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
	radix(m);
	for(int k=1,p=0;k<=n;k<<=1,m=p,p=0){
		for(int i=n-k+1;i<=n;i++)y[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++p]=sa[i]-k;
		radix(m);swap(x,y);x[sa[1]]=p=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=cmp(0)&&cmp(k)?p:++p;
		if(p==n)return;
	}
}
void geth(){
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	memset(h,0,sizeof(h));
	for(int i=1,k=0;i<=n;h[rk[i++]]=k)
	for(k?k--:1;i+k<=n&&s[i+k]==s[sa[rk[i]-1]+k];k++);
}
int in[N],mx[15],mn[15],m;
bool check(int len){
	memset(mx,0,sizeof(mx));
	memset(mn,0x3f,sizeof(mn));
	for(int i=2;i<=n+1;i++){
		if(h[i]<len){
			memset(mx,0,sizeof(mx));
			memset(mn,0x3f,sizeof(mn));
            mx[in[sa[i]]]=sa[i];  
            mn[in[sa[i]]]=sa[i];  
		}else{
            mx[in[sa[i]]]=max(mx[in[sa[i]]],sa[i]);  
            mn[in[sa[i]]]=min(mn[in[sa[i]]],sa[i]); 
            mx[in[sa[i-1]]]=max(mx[in[sa[i-1]]],sa[i-1]);  
            mn[in[sa[i-1]]]=min(mn[in[sa[i-1]]],sa[i-1]); 
			int j;
        	for(j=1;j<=m;j++)
        	if(mx[j]-mn[j]<len)break;
        	if(j>m)return true;
		}
	}
	return false;
}
int main(){
	int T;scanf("%d",&T);
	while(T--){
		scanf("%d",&m);
		n=1;
		int l=0,r=10000;
		for(int i=1;i<=m;i++){
			scanf("%s",s+n);
			int len=strlen(s+n);
			for(int j=n;j<n+len;j++){
				in[j]=i;
				s[j]-='a'-1;
			}
			s[n+=len]=i+26;
			n++;
		}
		n--;
		x=a;y=b;
		getsa(200);geth();
		int ans=0;
		while(l<=r){
			int mid=l+r>>1;
			if(check(mid)){l=mid+1;ans=mid;}
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

然后其实还有一个最小循环节和一个最长回文串,其实这两个用KMP和Manacher做就好了嘛,还快一点呢。


总结一下嘛,这类题无非就是 串接,二分答案,height分组,偶尔再来个单调栈什么的,其实做法都差不多,模型很相像,理解了height数组和sa数组的定义啊性质啊什么的就很好做(水)了。

好了是时候开启后缀自动机的大门了(还是好虚啊肿么办)

你可能感兴趣的:(后缀数组水水水水水水题)