• 後綴數組學習筆記


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

    一些基本的定義

    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

  • 相关阅读:
    1002CSP-S模拟测试赛后总结
    「题解」:X国的军队
    1001CSP-S模拟测试赛后总结
    「题解」:联
    0929CSP-S模拟测试赛后总结
    「题解」:Kill
    「题解」:y
    Censoring【自动AC机】【水题毁我青春】【20190614】
    传说级快读
    针对值域与下标关系的总结
  • 原文地址:https://www.cnblogs.com/ZeonfaiHo/p/6404282.html
Copyright © 2020-2023  润新知