後綴數組-DC3學習

後綴數組一直是字符串中重點的算法
後綴數組的一般求法是倍增,時間複雜度是O(nlog(n))O(nlog(n))
但是有的時候,這樣的算法複雜度及常數不能滿足題目要求
那我們就來學習一種線性的構造方法:DC3

算法的過程是這樣的,首先我們把後綴分成兩類,一類是下標是三的倍數的,另一類則不是。
我們先處理下標不是33的這一類,比如bacbccedbacbcced這個字符串(下標從00開始),令suf(i)suf(i)表示以ii下標開頭的後綴,則兩類分別是:
第一類 suf(1),suf(2),suf(4),suf(5),suf(7),suf(8)suf(1),suf(2),suf(4),suf(5),suf(7),suf(8)
第二類 suf(0),suf(3),suf(6)suf(0),suf(3),suf(6)
我們將suf(1),suf(2)suf(1),suf(2)先各自在串後面補比整個串小的字符使長度爲3的倍數,然後連起來,形成一個新串,如acbcced00cbccedacbcced00cbcced
然後我們發現每一個原來要排序的串等價於這個新串的suf(0),suf(3),suf(6)......suf'(0),suf'(3),suf'(6)......這樣的話我們就先把這個串變成一個三元組串,即a,c,ba,c,bc,c,ec,c,ed,0,0d,0,0c,b,cc,b,cc,e,dc,e,d
我們可以先基數排序,然後又轉化成了一個sa問題
接下來我們處理第二類
我們發現每個後綴可以轉化爲一個字符和一個已知的後綴,如suf(0)=s0+suf(1)suf(0)=s_0+suf(1)suf(3)=s3+suf(4)suf(3)=s_3+suf(4)sis_i表示ss的第ii個字符)
我們再基數排序就得到第二類的排名
最後我們歸併
考慮分類討論第一類後綴:
如果一個mod 3=1mod \ 3 = 1的後綴比較一個第二類後綴,那麼兩類後綴均可以轉化爲sx+suf(x+1)s_x+suf(x+1),可以比較
如果一個mod 3=2mod \ 3 = 2的後綴比較一個第二類後綴,那麼兩類後綴均可以轉化爲sx+sx+1+suf(x+2)s_x+s_{x+1}+suf(x+2),也可以比較
所以就做完了!
時間複雜度的話,可以通過等比數列求和等方法推出O(n)O(n)
最後附上莫名其妙寫了150150行的uoj模板

#include<iostream>
#include<cstring>
#include<cassert>
#include<cmath>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<time.h>
#include<algorithm>
using namespace std;
#define REP(i,x,y) for(int i=x;i<=y;i++)
#define rep(i,n) REP(i,1,n)
#define rep0(i,n) REP(i,0,n-1)
#define repG(i,x) for(int i=pos[x];~i;i=e[i].next)
#define ll long long
#define db double
const int N=1e6+7;
const int INF=1e9+7;
char ss[N];
int d[N];
int n;
struct SA{
	int ra[N*4],sa[N*4],a[N*4],p[N],rk[N],sk[N],ct[N],ls[N],nw[N],dr[N],ds[N],gs[N],gr[N],u[N];
	bool cmp1(int x1,int y1,int x2,int y2){
		if(x1!=x2)return x1<x2;
		return y1<y2;
	}
	bool cmp2(int x1,int y1,int z1,int x2,int y2,int z2){
		if(x1!=x2)return x1<x2;
		if(y1!=y2)return y1<y2;
		return z1<z2;
	}
	void sor(int L,int d,int o){//基數排序
		rep0(i,o+2)ct[i]=ls[i]=nw[i]=0;
		rep(i,L){
			nw[a[p[sk[i]]+d]+1]++;
			if(ct[a[p[sk[i]]+d]+1]!=rk[sk[i]]){
				ct[a[p[sk[i]]+d]+1]=rk[sk[i]];
				ls[a[p[sk[i]]+d]+1]=nw[a[p[sk[i]]+d]+1];
			}
			ds[sk[i]]=nw[a[p[sk[i]]+d]+1];
			dr[sk[i]]=ls[a[p[sk[i]]+d]+1];
		}
		rep(i,o+1)nw[i]+=nw[i-1];
		rep(i,L){
			sk[nw[a[p[i]+d]]+ds[i]]=i;
			rk[i]=nw[a[p[i]+d]]+dr[i];
		}
	}
	void build(int l,int r){
		int len=r-l+1,cnt=0,c1=0,h1=1,h2=1,h3=l;
		if(len==1){//邊界
			sa[l]=l;
			ra[l]=1;
			return;
		}
		REP(i,l,r){
			if((i-l)%3==0)continue;
			p[++cnt]=i;
			rk[cnt]=1;
			sk[cnt]=cnt;
		}
		for(int i=2;~i;i--)sor(cnt,i,len);
		bool fl=0;
		rep(i,cnt-1)if(rk[sk[i]]==rk[sk[i+1]]){fl=1; break;}
		if(fl==1){//沒排好再遞歸
			rep(i,cnt){
				if(i&1)a[r+4+i/2]=rk[i]+1;
				else a[r+4+(cnt+1)/2+i/2]=rk[i]+1;
			}
			a[r+4+(cnt+1)/2]=1;//中間記得添加分割
			build(r+4,r+cnt+4);
			rep(i,cnt){
				if(i&1)rk[i]=ra[r+4+i/2]-1;
				else rk[i]=ra[r+4+(cnt+1)/2+i/2]-1;
			}
			rep(i,cnt)sk[rk[i]]=i;
			cnt=0;
			REP(i,l,r)if((i-l)%3!=0)p[++cnt]=i;
		}
		rep0(i,cnt+1)nw[i]=0;
		rep(i,cnt){
			u[i]=p[i];
			gs[i]=sk[i];
			gr[i]=rk[i];
			if(i&1)nw[rk[i]]++;
		}
		if(r>u[cnt])nw[0]++;
		rep(i,cnt)nw[i]+=nw[i-1];
		rep(i,cnt){
			if(i&1){
				p[++c1]=p[i]-1;
				rk[c1]=nw[rk[i]];
				sk[rk[c1]]=c1;
			}
		}
		if(r>u[cnt]){
			p[++c1]=r;
			rk[c1]=1;
			sk[1]=c1;
		}
		sor(c1,0,len);//排序第二部分
		rep(i,cnt)ct[u[i]]=i;
		while(h1<=cnt&&h2<=c1){
			bool cmp;
			int v1=u[gs[h1]],v2=p[sk[h2]];//最冗長的歸併
			if((v1-l)%3==1){
				int u1=(v1==r)?0:gr[ct[v1+1]],u2=(v2==r)?0:gr[ct[v2+1]];
				cmp=cmp1(a[v1],u1,a[v2],u2);
			}
			else{
				int u1=(v1+2>r)?0:gr[ct[v1+2]],u2=(v2+2>r)?0:gr[ct[v2+2]];
				cmp=cmp2(a[v1],a[v1+1],u1,a[v2],a[v2+1],u2);
			}
			if(cmp)sa[h3++]=u[gs[h1++]];
			else sa[h3++]=p[sk[h2++]];
		}
		while(h1<=cnt)sa[h3++]=u[gs[h1++]];
		while(h2<=c1)sa[h3++]=p[sk[h2++]];
		REP(i,l,r)ra[sa[i]]=i-l+1;
	}
	int ht[N];
	void bht(){//求height數組
		int ans=0;
		rep(i,n){
			if(ra[i]==n){ans=0; continue;}
			int p=sa[ra[i]+1];
			if(ans)ans--;
			while(a[p+ans]==a[i+ans])ans++;
			ht[ra[i]]=ans;
		}
	}
}T;
int main(){
	scanf("%s",ss+1);
	n=strlen(ss+1);
	rep(i,n)d[ss[i]]++;
	rep(i,128)d[i]+=d[i-1];
	rep(i,n)T.a[i]=d[ss[i]];
	T.build(1,n);
	T.bht();
	rep(i,n)printf("%d ",T.sa[i]);
	puts("");
	rep(i,n-1)printf("%d ",T.ht[i]);
	puts("");
	return 0;
}

我想沒什麼人會看到這吧
但是我以後一定會把模板寫的更短更好看,常數更小
我好像跑不過倍增誒

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章