後綴數組小結

#前言 一道模板題 後綴數組(SA)是一個比較強大的處理字符串的算法,是有關字符串的比較基礎是嗎?算法,因此必須掌握 實現主要有倍增和$DC3$,而我太弱了只學了倍增 #目錄 ##知識點 ###1.基數排序+倍增 ###2.最長公共前綴Height ##一些要維護的東西 $s$:就是這個字符串,長度爲$len$ $rank[i]$:表示$i到len$這個後綴在全部後綴中的排名 $sa[i]$:表示排名爲$i$的後綴的首字符下標 $height[i]$:表示相鄰兩個排名的後綴的的最長公共前綴的長度 #基數排序+倍增構造SA 首先基數排序:就是低位到高位一位一位桶排,詳見baidu 怎麼排序? 若是一位一位來,那不就成了暴力了 因此咱們要倍增排序 具體來講,對於一個長度爲$2^k$的字符串,咱們能夠把它當作是兩個長度爲$2^{k-1}$的字符串組成的,那麼就至關於它有兩個關鍵字,咱們就從長度爲$1$開始,每次對這兩個關鍵字排序不就好了 一張古老的圖 看代碼c++

# include <bits/stdc++.h>
# define IL inline
# define RG register
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int _(1e6 + 5);

int len, sa[_], t[_], a[_], rk[_], y[_];
char s[_];
//t是桶;a和s同樣;rk就是rank;y是輔助rk的數組,和sa性質類似;sa就是sa

IL bool Cmp(RG int i, RG int j, RG int k){  return y[i] == y[j] && y[i + k] == y[j + k];  } //肯定兩個子串是否相同

IL void Sort(){
	RG int m = 80; //初始字符集大小
	for(RG int i = 1; i <= len; ++i) ++t[rk[i] = a[i]];
	for(RG int i = 1; i <= m; ++i) t[i] += t[i - 1];
	for(RG int i = len; i; --i) sa[t[rk[i]]--] = i;  //初始一個字符的排序
	for(RG int k = 1; k <= len; k <<= 1){  //倍增
		RG int l = 0;
		for(RG int i = 0; i <= m; ++i) y[i] = 0;
		//先按第二關鍵字排序,y記下編號
		for(RG int i = len - k + 1; i <= len; ++i) y[++l] = i;  //越界的第二關鍵字沒有,放前面
		for(RG int i = 1; i <= len; ++i) if(sa[i] > k) y[++l] = sa[i] - k;  //剩下的按順序排
		//再按rk第一關鍵字排序
		for(RG int i = 0; i <= m; ++i) t[i] = 0;
		for(RG int i = 1; i <= len; ++i) ++t[rk[y[i]]];
		for(RG int i = 1; i <= m; ++i) t[i] += t[i - 1];
		for(RG int i = len; i; --i) sa[t[rk[y[i]]]--] = y[i];
		//用此次的rk更新下次的
		swap(rk, y); rk[sa[1]] = l = 1;
		for(RG int i = 2; i <= len; ++i) rk[sa[i]] = Cmp(sa[i], sa[i - 1], k) ? l : ++l;  //相同的rk同樣
		if(l >= len) break;  //此時確定不用排了,你們都不一樣
		m = l;  //記錄下次排序的字符集大小
	}
}

int main(RG int argc, RG char* argv[]){
	scanf(" %s", s + 1); len = strlen(s + 1);
	for(RG int i = 1; i <= len; ++i) a[i] = s[i] - '0';
	Sort();
	for(RG int i = 1; i <= len; ++i) printf("%d ", sa[i]);
	return puts(""), 0;
}

有點難理解,多看幾個博客就能夠了 #最長公共前綴---Height 有個$sa$和$rank$並無什麼卵用,這個時候就有$Height$這個美妙的東西 怎麼求? 暴力求$O(n^2)$顯然不行 那麼這個時候要利用$h$的美妙性質:$h[i]≥h[i-1]-1$ 證實:設後綴$suf[k]和suf[i-1]$爲兩個相鄰排名的後綴,它們的最長公共前綴就是$h[i-1]$ 同時去掉第一個字符,那麼就是$suf[k+1]和suf[i]$,那它們兩個的最長公共前綴顯然就是$h[i-1]-1$ 因此$suf[i]$和排在它前面的後綴的最長公共前綴至少是$h[i-1]-1$算法

那麼$h$就能夠很快求出來了,那麼$height$也就能很快求出來了數組

for(RG int i = 1; i <= len; ++i){
		h[i] = max(0, h[i - 1] - 1);
		if(rk[i] == 1) continue;
		while(a[i + h[i]] == a[sa[rk[i] - 1] + h[i]]) ++h[i];
	}
	for(RG int i = 1; i <= len; ++i) height[i] = h[sa[i]];

剛剛學代碼比較醜,並且可能有問題

#應用 題去這裏找 題去這裏找 ##1.最長公共前綴 題:給定一個字符串,詢問某兩個後綴的最長公共前綴。 分析: 就是區間$height$的最小值,$RMQ$問題工具

重複子串:字符串A在字符串B中至少出現兩次,則稱A是B的重複子串。 ##2.可重疊最長重複子串 題:給定一個字符串,求最長重複子串,重複的兩個子串能夠重疊。 分析: 只須要求 height 數組裏的最大值便可 ##3.不可重疊最長重複子串 題:給定一個字符串,求最長重複子串,重複的兩個子串不能重疊。 分析: 考慮二分答案,每次二分一個答案$k$,把$height$按$>=k$分組 最長公共前綴不小於 k 的兩個後綴必定在同一組。對於每組後綴,只須判斷每一個後綴的 sa 值的最大值和最小值之差是否不小於k便可 另外:利用 height 值對後綴進行分組的方法很經常使用 ##4.可重疊的 k 次最長重複子串 題:給定一個字符串,求至少出現 k 次的最長重複子串,這 k 個子串能夠重疊 分析: 也是二分答案+分組,判斷有沒有一個組的後綴個數不小於 k ##5.不相同的子串的個數 題:給定一個字符串,求不相同的子串的個數。 分析: 每一個子串必定是某個後綴的前綴,也就是求全部後綴不一樣前綴的個數 每來一個後綴$suf(i)$就會有,$len-sa[i]+1$的新的前綴,又因爲有$height$個重複的,那麼就是$len-sa[i]+1-height$的貢獻spa

還有不少用法見下面的文獻.net


#參考文獻 羅穗騫 --算法合集之《後綴數組——處理字符串的有力工具》code

相關文章
相關標籤/搜索