后缀数组(SA)备忘
兩個讓我真正理解代碼的資料:
2009集訓隊論文 網上的經典應用都是從里面抄的,還把解釋給去掉了。。。真事屑
這篇博客 代碼注釋特別好
桶排換成快排的代碼,便于理解算法思想
,這里面要減去k的原因是 sa[i] 作為 sa[i]-k 的第二關鍵字
循環中m=x說明所有后綴長度為x的子串已經排好,要長度為更新2m的答案
"y[i]表示第二關鍵字排名為i的數,第一關鍵字的位置"
trick:
如果s[i]太大可以先離散化一下
本質不同的子串個數 = \(\frac{n\times (n+1)}{2}\) - height數組的和
get_SA里的Y和rnk每次用完要清零 否則多組數據會出鍋,字符串n+1位置記得弄成0
倒過來建SA可以實現一些例如查LCS之類的操作
把字符串拼起來建SA可以同時比較多個字符串的后綴,中間用不同的分隔符隔開(注意爆char)
struct SA {int k, sa[N], rnk[N], H[N], st[N][17];char s[N];bool cmp(int *y, int a, int b, int m) {return y[a] == y[b] && y[a + m] == y[b + m];}void Sort(int *x, int *y, int *rk) {static int C[N];for (int i = 0; i <= k; ++i) C[i] = 0;for (int i = 1; i <= n; ++i) ++C[rk[i]];for (int i = 1; i <= k; ++i) C[i] += C[i - 1];for (int i = n; i; --i) y[C[rk[x[i]]]--] = x[i];}void get_SA() {static int Y[N];int *y = Y, *rk = rnk;k = 128;for (int i = 1; i <= n; ++i) rk[i] = s[y[i] = i];Sort(y, sa, rk);for (int m = 1, p = 0; p < n; k = p, m <<= 1) {for (p = 0; p < m; ++p) y[p + 1] = n - m + p + 1;for (int i = 1; i <= n; ++i) if (sa[i] > m) y[++p] = sa[i] - m;Sort(y, sa, rk), swap(rk, y);rk[sa[p = 1]] = 1;for (int i = 2; i <= n; ++i) rk[sa[i]] = cmp(y, sa[i], sa[i - 1], m) ? p : ++p;}for (int i = 1; i <= n; ++i) rnk[sa[i]] = i, Y[i] = 0;}void get_H() {for (int i = 1, k = 0; i <= n; H[rnk[i++]] = k)for (k ? --k : 0; s[i + k] == s[sa[rnk[i] - 1] + k]; ++k);for (int i = 2; i <= n; ++i) st[i][0] = H[i];for (int j = 1; j <= __lg(n); ++j)for (int i = 2; i + (1 << j) - 1 <= n; ++i)st[i][j] = min(st[i][j - 1], st[i + (1 << j - 1)][j - 1]);}void clear() {memset(rnk, 0, sizeof rnk);}int lcp(int x, int y) {//求后綴x和y的lcpx = rnk[x], y = rnk[y];if (x > y) swap(x, y);++x;int k = __lg(y - x + 1);return min(st[x][k], st[y - (1 << k) + 1][k]);} };求height復雜度一句話證明:k減小O(n)次,增加O(n)次
下文引自ID為遠航之曲 (blog已經掛掉了)神犇的博客
能夠線性計算height[]的值的關鍵在于h的性質,即h[i]>=h[i-1]-1,下面具體分析一下這個不等式的由來。
我們先把要證什么放在這:對于第i個后綴,設j=sa[rank[i] – 1],也就是說j是i的按排名來的上一個字符串,按定義來i和j的最長公共前綴就是height[rank[i]],我們現在就是想知道height[rank[i]]至少是多少,而我們要證明的就是至少是height[rank[i-1]]-1。
好啦,現在開始證吧。
首先我們不妨設第i-1個字符串(這里以及后面指的“第?個字符串”不是按字典序排名來的,是按照首字符在字符串中的位置來的)按字典序排名來的前面的那個字符串是第k個字符串,注意k不一定是i-2,因為第k個字符串是按字典序排名來的i-1前面那個,并不是指在原字符串中位置在i-1前面的那個第i-2個字符串。
這時,依據height[]的定義,第k個字符串和第i-1個字符串的公共前綴自然是height[rank[i-1]],現在先討論一下第k+1個字符串和第i個字符串的關系。
第一種情況,第k個字符串和第i-1個字符串的首字符不同,那么第k+1個字符串的排名既可能在i的前面,也可能在i的后面,但沒有關系,因為height[rank[i-1]]就是0了呀,那么無論height[rank[i]]是多少都會有height[rank[i]]>=height[rank[i-1]]-1,也就是h[i]>=h[i-1]-1。
第二種情況,第k個字符串和第i-1個字符串的首字符相同,那么由于第k+1個字符串就是第k個字符串去掉首字符得到的,第i個字符串也是第i-1個字符串去掉首字符得到的,那么顯然第k+1個字符串要排在第i個字符串前面,要么就產生矛盾了。同時,第k個字符串和第i-1個字符串的最長公共前綴是height[rank[i-1]],那么自然第k+1個字符串和第i個字符串的最長公共前綴就是height[rank[i-1]]-1。
到此為止,第二種情況的證明還沒有完,我們可以試想一下,對于比第i個字符串的字典序排名更靠前的那些字符串,誰和第i個字符串的相似度最高(這里說的相似度是指最長公共前綴的長度)?顯然是排名緊鄰第i個字符串的那個字符串了呀,即sa[rank[i]-1]。也就是說sa[rank[i]]和sa[rank[i]-1]的最長公共前綴至少是height[rank[i-1]]-1,那么就有height[rank[i]]>=height[rank[i-1]]-1,也即h[i]>=h[i-1]-1。
轉載于:https://www.cnblogs.com/storz/p/10593908.html
總結
以上是生活随笔為你收集整理的后缀数组(SA)备忘的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端笔记—第15篇js中的DOM操作
- 下一篇: Python+Selenium操作sel