1.子串(注:串!=字串):字串 s 的子串r[i..j] ,i ≤ j,表示r 串中從 i 到 j 這一段,就是順次排列r[i],r[i+1],…,r[j] 形成的子串。2.字尾:字尾是指從某個位置 i 開始到整個串末尾結束的乙個特殊子串。字串r 的從 第 i 個字 符 開 始 的 後 綴 表 示 為 suffix(i) ,也 就 是suffix(i)=r[i..len(r)] 。
3.字尾陣列(sa[i]存放排名第i大的子串首字元下標):字尾陣列 sa 是乙個一維陣列,它儲存1..n 的某個排列 sa1,sa[2], ……,sa[n],並且保證suffix(sa[i]) < suffix(sa[i+1]),1 ≤ i < n。也就是將 s 的 n 個字尾從小到大進行排序之後把排好序的字尾的開頭位置順次放入sa 中。
4.名次陣列(rank[i]存放suffix(i)的優先順序):名次陣列 rank[i] 儲存的是 suffix(i) 在所有字尾中從小到大排列的 「 名次 」 。
注:這個是排序的關鍵字~(這句話是我們排序的重點)
目標是求得串的sa陣列和rank陣列:易知sa和rank互為逆操作,即sa[rank[i]] = i;rank[sa[i]] = i;(所以我們只要求得其一,就能o(n)算出另乙個)注:這個結論只在最後完成排序的時候符合。但sa和rank的定義一直都是適用的。原因是最後的時候不會存在相同(rank相等)的兩個子串。
1.設排序的的當前長度是h。suffix(i,h) 表示suffix(i)前h個字元(大於length會截斷)。2.先按h=1,對suffix(i,h)(0 < i < s.length)排序。
3.倍增長度h,利用之前排序h/2長度後得到的rank陣列作為關鍵字,把後h/2部分作為第二關鍵字,把前h/2部分作為第一關鍵字,對h長度的子串作排序。
4.由於是倍增長度,所以最多作logn次排序。
注:那麼複雜要做到nlogn,顯然排序要o(n),o(n)一般都選計數排序。
以下內容請按**手動模擬乙個串abab的構造過程,求sa陣列。這能讓你真正清楚字尾陣列實現的精妙所在。按h=1進行計數排序
// cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊。sa儲存的是排第i的是誰,
// #define rep(i,n) for(int i = 0;i < n; i++) ,
int *k = rk,*id = height,*r = res, *cnt = wa;//計數排序
rep(i,up) cnt[i] = 0;
rep(i,len) cnt[k[i] = w[i]]++;
rep(i,up) cnt[i+1] += cnt[i];
for(int i = len - 1; i >= 0; i--)
求第二關鍵字(想想為什麼構造w陣列的時候末尾要加個0)
//cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊,sa儲存的是排第i的是誰。
for(int i = len - d; i < len; i++) id[p++] = i;
rep(i,len) if(sa[i] >= d) id[p++] = sa[i] - d; //id儲存了按後h/2排序的的序列,即排第i的後h/2的是原陣列中的那個
rep(i,len) r[i] = k[id[i]]; //構造新的排序陣列
對新陣列排序
//cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊,sa儲存的是排第i的是誰
rep(i,up) cnt[i] = 0;
rep(i,len) cnt[r[i]]++;
rep(i,up) cnt[i+1] += cnt[i];
for(int i = len - 1; i >= 0; i--)
得到新的關鍵字(即按h長度排序後的離散序列)
//cnt是計數排序的輔助陣列,k是第一關鍵字,id是第二關鍵字下標陣列,r是以下標為第二關鍵字的新構陣列,w存放的是字串資訊,sa儲存的是排第i的是誰
swap(k,r);
p = 0;
k[sa[0]] = p++;
rep(i,len-1)
重複以上最後可以得到sa陣列sa和rank有什麼用?
求height陣列!!
height[i] 表示sa[i]和sa[i-1]的最長字首,height的構造看**手推一定能弄懂,自己找題目試試。
模板為了短小精悍,所以盡量壓縮了**。
#define rep(i,n) for(int i = 0;i < n; i++)
using
namespace
std;
const
int size = 200005,inf = 1
<<30;
int rk[size],sa[size],height[size],w[size],wa[size],res[size];
void getsa (int len,int up)
int d = 1,p = 0;
while(p < len)
swap(k,r);
p = 0;
k[sa[0]] = p++;
rep(i,len-1)
if(p >= len) return ;
d *= 2,up = p, p = 0;
}}void getheight(int len)
height[rk[i]] = p;
p = max(0,p - 1);
}}int getsuffix(char s)
w[len++] = 0;
getsa(len,up+1);
getheight(len);
return len;
}
最後給出幾題練習題。•poj 2774 –最長公共連續子串(入門題目)
•poj1743—最長不重疊重複子串(二分的判定要小心點,這題有點特別。)
•poj3294—出現次數超過一半的最長子串(判斷組中不同串出現次數的技巧很關鍵)
•poj3261—重複k次可重疊子串。(會了上面兩題,這題應該很簡單,可以試試用單調棧。)
•poj2758—字尾陣列+rmq(這題難度不在rmq,而在於寫**的能力和查詢的演算法實現。)
字尾陣列小結
搞了這麼多字尾陣列,寫個總結 其實羅穗賽的 裡已經都總結得很清楚了。我這裡對一些 的具體實現細節和一些要注意的地方做一些說明。字尾陣列很重要的三個陣列就是 rank,sa和height了 其中rank i 表示i這個字尾的排名,sa i 表示排在第i位的字尾的首字母位置,height i 表示排名第...
字尾陣列小結
一道模板題 字尾陣列 sa 是乙個比較強大的處理字串的演算法,是有關字串的比較基礎是嗎?演算法,所以必須掌握 實現主要有倍增和dc 3 而我太弱了只學了倍增 s 就是這個字串,長度為le nran k i 表示i到 len 這個字尾在所有字尾中的排名 sa i 表示排名為 i 的字尾的首字元下標 h...
字尾陣列小結
我為什麼要叫小結呢這明明就是個題解包啊 直接偷迪哥的就好辣 差異 大概是個板子,求出 height 陣列後直接單調棧即可 相似子串 首先 子串是字尾的字首 其次 每個字尾貢獻的本質不同子串的數量是 n i 1 he i 因為子串過多所以我們考慮運用上文性質來二分查排名 至於相似度 rmq 即可?sa...