1. 程式人生 > >[後綴數組]總結

[後綴數組]總結

出現 += n-k 相同 最大 字典 前綴和 倍增 type

Template

算法邏輯:

對長度為1時進行排序
倍增長度k
{
    按第二關鍵字將第一關鍵字排序
    求出2*k長度下的SA
    求出2*k長度下的rank(即x數組)
}

模板:

#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
#define pb push_back
#define debug(x) cerr<<#x<<"="<<x<<endl
typedef double db;
typedef long
long ll; typedef pair<int,int> P; const int MAXN=1e6+10; char s[MAXN]; int len,lmt,cnt[MAXN],sa[MAXN],x[MAXN],y[MAXN],cur; void solve() { for(int i=1;i<=len;i++) cnt[x[i]=s[i]]++; for(int i=1;i<=lmt;i++) cnt[i]+=cnt[i-1]; for(int i=len;i>=1;i--) sa[cnt[x[i]]
--]=i; for(int k=1;k<=len;k<<=1,lmt=cur) { cur=0; for(int i=len-k+1;i<=len;i++) y[++cur]=i; for(int i=1;i<=len;i++) if(sa[i]>k) y[++cur]=sa[i]-k; for(int i=1;i<=lmt;i++) cnt[i]=0; for(int i=1;i<=len;i++) cnt[x[i]]++;
for(int i=1;i<=lmt;i++) cnt[i]+=cnt[i-1]; //一定要按第二關鍵字從大往小枚舉! for(int i=len;i>=1;i--) sa[cnt[x[y[i]]]--]=y[i]; swap(x,y);cur=1;x[sa[1]]=1; for(int i=2;i<=len;i++) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?cur:++cur; if(cur==len) break; } } int main() { scanf("%s",s+1); len=strlen(s+1);lmt=130; solve(); for(int i=1;i<=len;i++) printf("%d ",sa[i]); return 0; }

註意:

1、可以利用基數排序直接算出排名

求出前綴和後其實就表示了該權值下的排名上界,每次將上界減一

因此要按第二關鍵字從大到小算SA

2、當已經出現$len$個$rank$時即可退出

Exercises

1、[BZOJ 4892]Dna

一共只有$n-m+1$個可能的串,加速比對過程即可

由於最大失配次數只有3次,因此可以利用$lcp$加速比對連續相同的部分!

復雜度降為$O(n*3*log(n))$

2、[Gym 101194F]

先用處理多字符串的套路連起來

找到第一個字符串的每個後綴在$rank$中最接近的不同字符串的後綴

那麽對於後綴$i$的最短長度即為$max(lcp(rk[i],l[rk[i]]),lcp(rk[i],r[rk[i]]))+1$

為保證字典序最小用$lcp$來$O(logn)$進行比較

3、[BZOJ 3277]

先連起來,對每個後綴$x$二分可行長度$len$

判斷長度是否可行,就是判斷$lcp(x,y)\ge len$的$y$是否存在於$k$個字符串

由於$lcp$的單調性,可以左右分別二分出$y$的可行區間$[L,R]$

通過預處理出$least[R]$表示包含$k$個字符串的最右點,判斷是否$L\le least[R]$即可

Tip1:可以發現同一字符串中$len[i]\ge len[i-1]-1$,這樣像推$height$一樣推$len$就能做到單$log$

Tip2:時刻註意自己枚舉的是排名還是原串位置

4、[Gym 101955B]

關鍵點在於誤差小於$1e-9$!

由於概率大於0.5的只能有一個字母,將原始字母設為概率最大後失配必然使得相乘概率小於0.5

也就是說,最多失配30個字母就可以不用統計了!這樣就和前面的[BZOJ 4892]相同了

Tip:為保證精度可以用指數運算,這樣就能使用前綴和了

5、[Gym 102028H]

知道本質不同的字符串的算法即可

問題轉化為左端點固定,右端點為一個區間的最大值的和

可以發現對於此問題明顯只有單調棧中的元素有分段的貢獻

從而可以從後往前維護一個單調減的單調棧,每次退棧時用線段樹區間修改即可

6、[BZOJ 3238]

求$\sum lcp(i,j)$可以轉化為每個$height$的貢獻

由於$lcp$的單調性,對於每個$i$用單調棧找到其提供貢獻的區間$[L,R]$

最終答案即為$\sum (i-L+1)*(R-i+1)*h[i]$

Tip1:單調棧判斷時保證一個小於等於一個小於,防止相等時重復計數

Tip2:註意$h[i]$是與前一位的$lcp$,計數時按兩兩間理解,自己乘自己是允許的

7、[BZOJ 4310]

在本質不同的字符串序列中二分排名,每次先算出該字符串的位置

判斷就是從後往前每次加一個字符,只要字典序比二分串大就分割一段,判斷段數是否滿足

Tip:註意對兩相同位置求$lcp$時特殊處理!

8、[BZOJ 3879]

將查詢串按$rank$排序,算出兩兩間的$lcp$作為$height$

接下來就和[BZOJ 3238]完全相同了

9、[BZOJ 4278]

用$rank$的比較快速判斷兩後綴字典序的大小,貪心選擇字典序小的即可

10、[BZOJ 2320]

11、[BZOJ 2119]

[後綴數組]總結