後綴數組學習筆記

@(學習筆記)[后缀数组]

一些基本的定義

LCP: Longest Common Prifix最长公共前缀
后缀数组\(sa[i]\): 表示排名为\(i\)的后缀的起始坐標.
名次數組\(rank[i]\): 表示以\(i\)為起始的後綴的排名
*Hint: 上述的"排名"是指字符串從前往後的按照字典序的排名
簡單來說, 就是: 後綴數組\(sa[i]\)表示"排名第幾的是誰", 名次數組\(rank[i]\)表示"你排第幾". 值得注意的是, 即便是對於相同的後綴串, sa也必然是不同的, 而對應後綴相同的rank值則是相同的(詳見代碼, 這樣的原因是能方便後續處理).
這裡就有了一些性質:

  1. \(LCP(i, j) = LCP(j, i)\)
  2. \(LCP(i, i) = strlen(suffix(sa[i])) = n - sa[i] + 1\)

height數組 & h數組:

height數組定義: $$height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀(LCP)$$也就是說, \(height[i]\)表示排名相鄰的兩個後綴的最長公共前綴.
對於\(j < k\), 不妨設\(rank[j] < rank[k]\), 則有性質: suffix(j)和suffix(k)的最长公共前缀为\(height[rank[j]+1], height[rank[j]+2], height[rank[j]+3], .., height[rank[k]]\)中的最小值(原因稍加思考即可得到, 在此不作過多說明).

\(h[i]\): 表示\(suffix(i)\)與排名前一位的LCP的值. 即: $$h[i] = height[rank[i]]$$. 則\(h[i]\)有性質: $$h[i] \ge h[i - 1] - 1$$
然而: 在代碼實現時我們通常並不需要儲存h數組..

從某種程度上說, 後綴數組存在的意義就是為了計算height數組和h數組的值.

實現

這裡的是倍增的做法.

#include<cstdio>
#include<cctype>
#include<cstring>
using namespace std; 

inline char* read(char *s)
{
	char c;
	while(! isgraph(c = getchar()));
	int len = 0;
	while(isgraph(c))
		s[len ++] = c, c = getchar();
	return s;
}

const int L = 1 << 10;

char s[L];
int lenS; 

int sum[L];
int sa[L], rank[L], tmpSa[L], tmpRank[L];

void getSuffixArray()
{
	memset(sa, - 1, sizeof(sa));
	memset(rank, - 1, sizeof(rank));
	memset(tmpSa, - 1, sizeof(tmpSa));
	memset(tmpRank, - 1, sizeof(tmpRank));
	
	memset(sum, 0, sizeof(sum)); //初始化
	
	//此时排序只关注单个字符,所以每个起点为i长度为1的串的排名就是s[i]本身
	for(int i = 0; i < lenS; i ++)
		sum[s[i]] ++;
	for(int i = 1; i < LIM; i ++) //注意訪問不要越界
		sum[i] += sum[i - 1];
	for(int i = lenS - 1; ~ i; i --)
		sa[-- sum[s[i]]] = i;
	
	//更新rank數組
	rank[sa[0]] = 0;
	int p = 0;
	for(int i = 1; i < lenS; i ++)
		rank[sa[i]] = s[sa[i]] != s[sa[i - 1]] ? ++p : p;
	int lim = p + 1;
	
	//開始倍增
	for(int i = 1; lim < lenS; i <<= 1)
	{
		//對於每一次倍增, 維護的後綴串的長度為i * 2
		//下面一段是基數排序後綴串后i位的過程
		int p = 0;
		for(int j = lenS - i; j < lenS; j ++)
			tmpSa[p ++] = j;
		//上一句: 假如后i位直接為空的話, 排到最前面
		//下一句: 假如不為空的話, 根據上一次的結果來排 
		for(int j = 0; j < lenS; j ++)
			if(sa[j] >= i)
				tmpSa[p ++] = sa[j] - i;
				
		//下面一段: 初始化, 接著要把后i位的排序與前i位合併
		memset(sum, 0, sizeof(sum));
			
		//這一段就是把后i位與前i位合併的過程
		for(int j = 0; j < lenS; j ++)
			sum[rank[j]] ++;
		//記錄每一種權值的數量 
		for(int j = 1; j < lim; j ++)
			sum[j] += sum[j - 1];
		//求出前綴和 
		for(int j = lenS - 1; ~ j; j --)
			sa[-- sum[rank[tmpSa[j]]]] = tmpSa[j];
		//求解sa數組 
		
		//下面一段: 更新rank數組
		memcpy(tmpRank, rank, sizeof(rank)); //tmpRank只是一個臨時空間, 用於備份
		 
		p = 0;
		rank[sa[0]] = p;
		for(int j = 1; j < lenS; j ++)
		{
			if(tmpRank[sa[j]] != tmpRank[sa[j - 1]] || tmpRank[sa[j] + i] != tmpRank[sa[j - 1] + i])
				p ++;
			rank[sa[j]] = p;
		}
		lim = p + 1;
	}
}

int height[L];

//根據sa數組求出height值
//這一段要背下來 
void getHeight()
{
	int p = 0;
	for(int i = 0; i < lenS; i ++)
		if(rank[i])
		{
			if(p)
				p --;
			
			while(s[i + p] == s[sa[rank[i] - 1] + p])
				p ++;
			height[rank[i]] = p;
		}
}

int main()
{
	read(s);
	lenS = strlen(s);
	getSuffixArray();
	getHeight();
}

例題

模板題
http://www.cnblogs.com/ZeonfaiHo/p/6410536.html

posted @ 2017-02-16 09:33  Zeonfai  阅读(108)  评论(0编辑  收藏  举报