字尾陣列 最詳細講解

2021-09-25 22:37:59 字數 4349 閱讀 3628

我們先看幾條定義:

在字串s中,取任意i<=j,那麼在s中擷取從i到j的這一段就叫做s的乙個子串

字尾就是從字串的某個位置i到字串末尾的子串,我們定義以s的第i個字元為第乙個元素的字尾為suff(i)

把s的每個字尾按照字典序排序,

字尾陣列sa[i]就表示排名為i的字尾的起始位置的下標

而它的對映陣列rk[i]就表示起始位置的下標為i的字尾的排名

簡單來說,sa表示排名為i的是啥,rk表示第i個的排名是啥

一定要記牢這些陣列的意思,後面看**的時候如果記不牢的話就絕對看不懂

先說最暴力的情況,快排(n log n)每個字尾,但是這是字串,所以比較任意兩個字尾的複雜度其實是o(n),這樣一來就是接近o(n^2 log n)的複雜度,資料大了肯定是不行的,所以我們這裡有兩個優化。

ps:本文中的^表示平方而不是異或

首先讀入字串之後我們現根據單個字元排序,當然也可以理解為先按照每個字尾的第乙個字元排序。對於每個字元,我們按照字典序給乙個排名(當然可以並列),這裡稱作關鍵字。

接下來我們再把相鄰的兩個關鍵字合併到一起,就相當於根據每乙個字尾的前兩個字元進行排序。想想看,這樣就是以第乙個字元(也就是自己本身)的排名為第一關鍵字,以第二個字元的排名為第二關鍵字,把組成的新數排完序之後再次標號。沒有第二關鍵字的補零。

既然是倍增,就要有點倍增的樣子。接下來我們對於乙個在第i位上的關鍵字,它的第二關鍵字就是第(i+2)位置上的,聯想一下,因為現在第i位上的關鍵字是suff(i)的前兩個字元的排名,第i+2位置上的關鍵字是suff(i+2)的前兩個字元的排名,這兩個一合併,不就是suff(i)的前四個字元的排名嗎?方法同上,排序之後重新標號,沒有第二關鍵字的補零。同理我們可以證明,下一次我們要合併的是第i位和第i+4位,以此類推即可……

ps:本文中的「第i位」表示下標而不是排名。排名的話我會說「排名為i」

那麼我們什麼時候結束呢?很簡單,當所有的排名都不同的時候我們直接退出就可以了,因為已經排好了。

顯然這樣排序的速度穩定在(log n)

如果我們用快排的話,複雜度就是(n log^2 n) 還是太大。

這裡我們用一波基數排序優化一下。在這裡我們可以注意到,每一次排序都是排兩位數,所以基數排序可以將它優化到o(n)級別,總複雜度就是(n log n)。

介紹一下什麼是基數排序,這裡就拿兩位數舉例

我們要建兩個桶,乙個裝個位,乙個裝十位,我們先把數加到個位桶裡面,再加到十位桶裡面,這樣就能保證對於每個十位桶,桶內的順序肯定是按個位公升序的,很好理解。

話說這個費了我好長時間,就為了證明幾條定理……懶得證明的話背過就行了,不過筆者還是覺得知道證明用起來更踏實一些,話說我的證明過程應該比較好懂,適合初學者理解……

我們定義lcp(i,j)為suff(sa[i])與suff(sa[j])的最長公共字首

字尾陣列這個東西,不可能只讓你排個序就完事了……大多數情況下我們都需要用到這個輔助工具lcp來做題的

顯而易見的

lcp(i,j)=lcp(j,i);

lcp(i,i)=len(sa[i])=n-sa[i]+1;

這兩條性質有什麼用呢?對於i>j的情況,我們可以把它轉化成i我們每次依次比較字元肯定是不行的,單次複雜度為o(n),太高了,所以我們要做一定的預處理才行。

lcp lemma

lcp(i,k)=min(lcp(i,j),lcp(j,k)) 對於任意1<=i<=j<=k<=n

證明:設p=min,則有lcp(i,j)≥p,lcp(j,k)≥p。

設suff(sa[i])=u,suff(sa[j])=v,suff(sa[k])=w;

所以u和v的前p個字元相等,v和w的前p個字元相等

所以u和w的前p的字元相等,lcp(i,k)>=p

設lcp(i,k)=q>p 那麼q>=p+1

因為p=min,所以u[p+1]!=v[p+1] 或者 v[p+1]!=w[p+1]

但是u[p+1]=w[p+1] 這不就自相矛盾了嗎

所以lcp(i,k)<=p

綜上所述lcp(i,k)=p=min

lcp theorem

lcp(i,k)=min(lcp(j,j-1)) 對於任意1這個結合lcp lemma就很好理解了

我們可以把i~k拆成兩部分i~(i+1)以及(i+1)~k

那麼lcp(i,k)=min(lcp(i,i+1),lcp(i+1,k))

我們可以把(i+1)~k再拆,這樣就像乙個dp,正確性顯然

我們設height[i]為lcp(i,i-1),1由lcp theorem可得,lcp(i,k)=min(height[j]) i+1<=j<=k

那麼height怎麼求,列舉嗎?nonono,我們要利用這些字尾之間的聯絡

設h[i]=height[rk[i]],同樣的,height[i]=h[sa[i]];

那麼現在來證明最關鍵的一條定理:

h[i]>=h[i-1]-1;

證明過程來自曲神學長的blog,我做了一點改動方便初學者理解:

首先我們不妨設第i-1個字串按排名來的前面的那個字串是第k個字串,注意k不一定是i-2,因為第k個字串是按字典序排名來的i-1前面那個,並不是指在原字串中位置在i-1前面的那個第i-2個字串。

這時,依據height的定義,第k個字串和第i-1個字串的公共字首自然是height[rk[i-1]],現在先討論一下第k+1個字串和第i個字串的關係。

第一種情況,第k個字串和第i-1個字串的首字元不同,那麼第k+1個字串的排名既可能在i的前面,也可能在i的後面,但沒有關係,因為height[rk[i-1]]就是0了呀,那麼無論height[rk[i]]是多少都會有height[rk[i]]>=height[rk[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[rk[i-1]],

那麼自然第k+1個字串和第i個字串的最長公共字首就是height[rk[i-1]]-1。

到此為止,第二種情況的證明還沒有完,我們可以試想一下,對於比第i個字串的排名更靠前的那些字串,誰和第i個字串的相似度最高(這裡說的相似度是指最長公共字首的長度)?顯然是排名緊鄰第i個字串的那個字串了呀,即sa[rank[i]-1]。但是我們前面求得,有乙個排在i前面的字串k+1,lcp(rk[i],rk[k+1])=height[rk[i-1]]-1;

又因為height[rk[i]]=lcp(i,i-1)>=lcp(i,k+1)

所以height[rk[i]]>=height[rk[i-1]]-1,也即h[i]>=h[i-1]-1。

例題:luogu3809 字尾排序

注意上面那個題不用求lcp……看**建議先大略掃一遍,因為的確有點繞

#include#include#include#define rint register int

#define inv inline void

#define ini inline int

#define maxn 1000050

using namespace std;

char s[maxn];

int y[maxn],x[maxn],c[maxn],sa[maxn],rk[maxn],height[maxn],wt[30];

int n,m;

inv putout(int x)

rint l=0;

while(x) wt[++l]=x%10,x/=10;

while(l) putchar(wt[l--]+48);

}inv get_sa()

for (rint i=1; i<=n; ++i) putout(sa[i]),putchar(' ');

}inv get_height()

putchar(10);

for (rint i=1; i<=n; ++i) putout(height[i]),putchar(' ');

}int main()

字尾陣列 講解

子串 從原串中選取連續的一段,即子串 空串也是子串 字尾 suf k 為s k n 構成的子串 任何子串都是某個字尾的字首 最長公共字首lcp suf i suf j 將所有字尾suf 1 suf 2 suf n 按照字典序從小到大排序 暴力sortn2 logn 二分 hash nlog2n cm...

史上最詳細webpack講解

webpack是前端方面的靜態資源打包工具,能夠讓瀏覽器也支援模組化,他會根據模組的依賴關係進行靜態分析,然後按照某種規則生成靜態資源 安裝webpack 安裝webpack npm install g webpack 或者 安裝最新版webpack npm install g webpack 如果...

webview最全面,最詳細講解

首先先看一下webview的詳解 1.簡介 webview是乙個基於webkit引擎 展現web頁面的控制項。android的webview在低版本和高版本採用了不同的webkit版本核心,4.4後直接使用了chrome。2.作用webview控制項功能強大,除了具有一般view的屬性和設定外,還可...