后缀数组 SA

发布时间 2023-10-16 20:24:10作者: ChElsYqwq

膜拜 zxy,1h 学会 SA。这玩意真的好绕啊 >w<

给定一个字符串 \(S\),设 \(S(l,r)\) 表示 \(S_l\dots S_r\) 组成的字符串,\(s(i)\) 表示 \(S(i,n)\)

\(s(1),\dots,s(n)\) 排序,设 \(sa[i]\) 表示排名为 \(i\) 的字符串为 \(s(sa[i])\)

我们顺次考虑按照前 \(1,2,4,8,\dots,k\) 位 排好序的情况,那我们的问题是 how 倍增求解 qwq

我们知道 \(S(i,i+k-1)\)\(S(i+k,i+2*k-1)\) 在长度为 \(k\) 下的排名,想要合并显然是先看前者再看后者即可,设前者为第一关键字,后者为第二关键字,一次合并 \(O(n)\),总的复杂度 \(O(n\log n)\)

\(sa(i)\) 表示排名为 \(i\) 的字符串在原串中的起始位置。

\(x(i)\) 表示排名为 \(i\) 的第一关键字,\(y(i)\) 是排名为 \(i\) 的第二关键字。

n=strlen(s+1), m=122; // m 是字符种类数 
up(i,1,n) x[i]=s[i], c[x[i]]++; 
up(i,2,m) c[i]+=c[i-1];
up(i,1,n) sa[c[x[i]]--]=i; // 按 s(i,i) 缔造初始 SA  
for(int k=1; k<=n; k<<=1) { // 倍增长度 
	int num=0;
	up(i,n-k+1,n) y[++num]=i; // 这些位置的第二关键字为 null,是极小的,先对其进行赋值 o.O 
	up(i,1,n) if(sa[i]>k) y[++num]=sa[i]-k; // 以 sa[i] 作为第二关键字的情况 
	up(i,1,m) c[i]=0;  
	up(i,1,n) c[x[i]]++;
	up(i,2,m) c[i]+=c[i-1];
	down(i,n,1) sa[c[x[y[i]]]--]=y[i]; // 重构排名 SA,y 越大的排名越大 
	up(i,1,n) y[i]=x[i], x[i]=0; // from x to y ~ 
	num=1, x[sa[1]]=1; 
	up(i,2,n) {
		if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=num;
		else x[sa[i]]=++num;
	} // 数一下排名相同的多少个
	if(num==n) break; m=num; // 更新种类数 
}
up(i,1,n) cout << sa[i] << ' '; // perfect!