[模板] 后缀数组

概述

后缀数组(Suffix Array), 是一种将字符串所有后缀排序的一种算法。
通过排序后的后缀,我们可以得到字符串的许多性质,如重复出现的字串等。

算法

(n) 表示字符串长度, (Sigma) 表示字符集大小.

常见的后缀数组算法有:

  1. 倍增算法
    • 时间复杂度 $ O(n log n) $
    • 常数较小
    • 代码较短
  2. DC3算法
    • 时间复杂度 $ O(n) $
    • 常数较大
    • 代码较长
  3. SA-IS算法
    • 时间复杂度 $ O(n) $
    • 常数中等
    • 代码较长
    • 窝三个月之前还会,现在就不会了
  4. 后缀自动机遍历
    • 时间复杂度 (O(n*Sigma)) , 空间复杂度 (O(n*Sigma))
    • 或者时间复杂度 (O(n log n)) , 空间复杂度 (O(n)) (map实现)
    • 常数中等
    • 代码较长
    • 但是并没有倍增好写

倍增算法

放两个链接:

height

(S_i) 表示 (S) 的第 (i) 个后缀.

定义

[height(i) = lcp(S_{sa(i)}, S_{sa(i-1)}) ]

[height(rk(i)) ge height(rk(i-1)) - 1 ]

代码

#define rep(i,l,r) for(register int i=(l);i<=(r);++i)
#define repdo(i,l,r) for(register int i=(l);i>=(r);--i)

const int ssz=1e6+5;
char s[ssz];

struct tsa{
	int n,sigma,t1[ssz],t2[ssz],sa[ssz],c[ssz],*rk=t1,*tp=t2;
	int hi[ssz];
	int l2n[ssz],stt[21][ssz];
	void pr(){
		rep(i,1,n)printf("%d %d %d
",sa[i],rk[i],tp[i]);
	}
	//from http://www.cnblogs.com/zwfymqz/p/8413523.html
	//sa[i]: 长度为w的后缀中, 排名为i的后缀的位置
	//rk[i]: 长度为w的后缀中, 从第i个位置开始的后缀的排名
	//tp[i]: 长度为2w的后缀中, 第二关键字排名为i的后缀的位置
	void rsort(){//sort i([1,n]) by (rk[i],inv_tp[i])
		rep(i,0,sigma)c[i]=0;
		rep(i,1,n)++c[rk[i]];
		rep(i,1,sigma)c[i]+=c[i-1];
		repdo(i,n,1)sa[c[rk[tp[i]]]--]=tp[i];
	}
	void suffixsort(char *s){
		rep(i,1,n)rk[i]=s[i],tp[i]=i;
		rsort();
		for(int w=1,p=0;p<n;sigma=p,w<<=1){
			p=0;
			rep(i,1,w)tp[++p]=n-w+i; //不存在后w个字符的串的第二关键字最小
			rep(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w; //前w个字符不会作为第二关键字 
			rsort();
			//calc rk (|第一关键字|=2w)
			swap(tp,rk);//del tp; tp=rk(|第一关键字|=w) 
			p=1,rk[sa[1]]=p;
			rep(i,2,n){
				rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
			}
		}
	}
	
	void gethi(char *s){
		int k=0;
		rep(i,1,n){
			if(k>0)--k;
			while(s[i+k]==s[sa[rk[i]-1]+k])++k;
			hi[rk[i]]=k;
		}
	}
	
	void rmq(){
		int l=0;
		rep(i,1,n)stt[0][i]=hi[i],l2n[i]=(i==(1<<(l+1))?++l:l);
		rep(i,1,l2n[n]){
			repdo(j,n-(1<<i)+1,1){
				stt[i][j]=min(stt[i-1][j],stt[i-1][j+(1<<(i-1))]);
			}
		}
	}
	
	int lcp(int x,int y){//x&y are ranks
		if(x==y)return n-sa[x]+1;
		if(x>y)swap(x,y);
		++x;
		int l=l2n[y-x+1];
		return min(stt[l][x],stt[l][y-(1<<l)+1]);
	}
	
	int lcp1(int x,int y,int l1,int l2){
		return min(lcp(x,y),min(l1,l2));
	}
	//O(n log n + sigma)
	void init(char *s,int n0,int sig0){
		sigma=sig0,n=n0;
		suffixsort(s);
		gethi(s);
		rmq();
	}
}sa;

//init e.g.
	sig0=27;
	rep(i,1,n)s[i]-='a'-1;
	a.init(s,n,sig0);

for copying

const int ssz=1e6+5;
char s[ssz];

struct tsa{
	int n,sigma,t1[ssz],t2[ssz],sa[ssz],c[ssz],*rk=t1,*tp=t2;
	int hi[ssz];
	int l2n[ssz],stt[21][ssz];
	void pr(){
		rep(i,1,n)printf("%d %d %d
",sa[i],rk[i],tp[i]);
	}
	void rsort(){
		rep(i,0,sigma)c[i]=0;
		rep(i,1,n)++c[rk[i]];
		rep(i,1,sigma)c[i]+=c[i-1];
		repdo(i,n,1)sa[c[rk[tp[i]]]--]=tp[i];
	}
	void suffixsort(char *s){
		rep(i,1,n)rk[i]=s[i],tp[i]=i;
		rsort();
		for(int w=1,p=0;p<n;sigma=p,w<<=1){
			p=0;
			rep(i,1,w)tp[++p]=n-w+i;
			rep(i,1,n)if(sa[i]>w)tp[++p]=sa[i]-w;
			rsort();
			swap(tp,rk);
			p=1,rk[sa[1]]=p;
			rep(i,2,n){
				rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
			}
		}
	}
	void gethi(char *s){
		int k=0;
		rep(i,1,n){
			if(k>0)--k;
			while(s[i+k]==s[sa[rk[i]-1]+k])++k;
			hi[rk[i]]=k;
		}
	}
	void rmq(){
		int l=0;
		rep(i,1,n)stt[0][i]=hi[i],l2n[i]=(i==(1<<(l+1))?++l:l);
		rep(i,1,l2n[n]){
			repdo(j,n-(1<<i)+1,1){
				stt[i][j]=min(stt[i-1][j],stt[i-1][j+(1<<(i-1))]);
			}
		}
	}
	
	int lcp(int x,int y){//x&y are ranks
		if(x==y)return n-sa[x]+1;
		if(x>y)swap(x,y);
		++x;
		int l=l2n[y-x+1];
		return min(stt[l][x],stt[l][y-(1<<l)+1]);
	}

	int lcp1(int x,int y,int l1,int l2){
		return min(lcp(x,y),min(l1,l2));
	}

	void init(char *s,int n0,int sig0){
		sigma=sig0,n=n0;
		suffixsort(s);
		gethi(s);
		rmq();
	}
}sa;

SA-IS

说不定什么时候窝就回来填坑了

原文地址:https://www.cnblogs.com/ubospica/p/10162594.html