我們知道乙個**旋律被表示為長度為 n 的數構成的數列。
旋律是一段連續的數列,相似的旋律在原數列可重疊。比如在1 2 3 2 3 2 1 中 2 3 2 出現了兩次。
怎麼知道一段旋律中出現次數至少為k次的旋律最長是多少?
輸入第一行兩個整數 n和k。1≤n≤20000 1≤k≤n
接下來有 n 個整數,表示每個音的數字。1≤數字≤100
輸出一行乙個整數,表示答案。
樣例輸入
8 212樣例輸出3232
31
4這次的問題被稱為最長可重疊重複k次子串問題。
顧名思義,字尾陣列就是記錄所有字尾的陣列,同時,它也是有序的。字尾陣列 sa 可以幫助我們解決單字串問題、兩個字串的問題和多個字串的問題等。
比如說字串banana$,我們暫且把$認為是乙個字元(表示字串結尾)。我們記suffix(i)表示從原字串第i個字元開始到字串結尾的字尾。我們把它所有的字尾拿出來按字典序排序:字尾i
$7a$6
ana$
4anana$
2banana$
1na$
5nana$3
並且我們把排好序的陣列記做sa。比如sa[1]=7,sa[4]=2。
另外,字尾陣列會順便記錄名次陣列 rank, rank[i] 儲存的是字尾 i 在所有字尾中從小到大排列的「名次」。比如上個字串中rank[7]=1,rank[4]=3
我們現在令 height[i] 是 suffix(sa[i-1]) 和 suffix(sa[i]) 的最長公共字首長度,即排名相鄰的兩個字尾的最長公共字首長度。比如height[4]就是anana$和ana$的最長公共字首,也就是ana,長度為3。你注意,這個height陣列有乙個神奇的性質:若 rank[j] < rank[k],則字尾 sj..n 和 sk..n 的最長公共字首為 min。這個性質是顯然的,因為我們已經字尾按字典序排列。
我們有如下結論:height[rank[i]] ≥ height[rank[i-1]]-1。
設suffix(k)是排在suffix(i-1)前一名的字尾,則它們的最長公共字首是height[rank[i-1]]。那麼suffix(k+1)將排在suffix(i)的前面(這裡要求height[rank[i-1]]>1,如果height[rank[i-1]]≤1,原式顯然成立)並且suffix(k+1)和suffix(i)的最長公共字首是height[rank[i-1]]-1,所以suffix(i)和在它前一名的字尾的最長公共字首至少是height[rank[i-1]]-1。
這樣我們按照 height[rank[1]], height[rank[2]] ... height[rank[n]] 的順序計算,利用height陣列的性質,就可以將時間複雜度可以降為 o(n)。這是因為height陣列的值最多不超過n,每次計算結束我們只會減1,所以總的運算不會超過2n次。
有了height,求最長可重疊重複k次子串就方便了。重複子串即兩字尾的公共字首,最長重複子串,等價於兩字尾的最長公共字首的最大值。問題就轉化成了,求height 陣列中最大的長度為 k的子串行的最小值。
字尾陣列的求法有很多,最有名的是兩種倍增演算法和dc演算法。時間複雜度上dc演算法更優,但是很複雜。我們這裡只介紹相對容易的倍增演算法。
簡單來說,倍增演算法分以下四步
對長度為 20=1 的字串,也就是所有單字母排序。
用長度為 20=1 的字串,對長度為 21=2 的字串進行雙關鍵字排序。考慮到時間效率,我們一般用基數排序。
用長度為 2k-1 的字串,對長度為 2k 的字串進行雙關鍵字排序。
直到 2k ≥ n,或者名次陣列 rank 已經從 1 排到 n,得到最終的字尾陣列。
以字串 "aabaaaab" 為例, 整個過程如圖所示。 其中 x、 y 是表示長度為 2k 的字串的兩個關鍵字。
感覺這個演算法就是利用已用的字尾排序資訊來更新更長串的排序資訊。
其實height陣列表示所有字尾排序後,每個字尾字串與前乙個的最長公共字首,所以height中最大值可以看做重複兩次的字串的最長長度(可以重疊),如果繼續對height陣列相鄰項取最小值,其中最大值就表示重複三次的字串的最長長度,所以求重複k次的最長長度,就可以重複k-1次取相鄰的最小值,再求最大值。
關於字尾陣列的詳細解說,請參考
先提供我一開始的簡單實現,效率比較低:
#include #include #include #include #include #include #define max 20010
using namespace std;
//file *stream;
int n, k;
string s, c;
int sa[max];//字尾陣列,儲存排序後字尾字串的開頭位置,本身下標對應名次
int rank[max];//名次陣列,儲存排序後字尾字串名次,本身下標對應字串開頭位置
int height[max];//排名相鄰的兩個字尾的最長公共字首
mapm;
void solve()
for (i = 1, j = 0; i <= n; i++)
int maxlen = -1;
while (k-- > 1) }
cout << maxlen << endl;
}int main()
n++;
if (k > 1)
solve();
else
cout << n - 1 << endl;
//freopen_s(&stream, "con", "r", stdin);
//system("pause");
return 0;
}
較高效的倍增法,但是比較難理解,而且利用的中間陣列比較多,易弄錯,不過可以拿過來用:
const int n = 100000 + 50;//後續的規模更大,體現倍增法的優勢
int sa[n];//字尾陣列,儲存排序後字尾字串的開頭位置,本身下標對應名次
int rank[n];//名次陣列,儲存排序後字尾字串名次,本身下標對應字串開頭位置
int height[n];//排名相鄰的兩個字尾的最長公共字首
int wa[n], wb[n], wss[n], wv[n];
int aa[n];
int n;
int cmp(int *r, int a, int b, int l)
void getsa(int *r, int *sa, int n, int m)//r為初始輸入,可以對應改為字串陣列,sa為字尾陣列,n為輸入個數+1,m為輸入中的最大值,字元的話可以對應改為ascii碼最大值{
int i, j, p, *x = wa, *y = wb, *t;
for (i = 0; i= 0; i--) sa[--wss[x[i]]] = i;
for (j = 1, p = 1; p= j) y[p++] = sa[i] - j;
for (i = 0; i= 0; i--) sa[--wss[wv[i]]] = y[i]; //基數排序部分
for (t = x, x = y, y = t, p = 1, x[sa[0]] = 0, i = 1; i
Hiho 120 字尾陣列一 重複旋律
找連續k個中的最小值的最大值 根據height陣列的性質,若 rank j rank k 則字尾 s j.n 和 sk.n 的最長公共字首為 min。這個性質存在是因為我們對字尾按字典序排序。所以連續k個的height最小值代表出現至少為k次的旋律,再在裡頭找最大值,代表找出現次數至少為k次的最長旋...
字尾陣列四 重複旋律4
我們知道乙個 旋律被表示為長度為 n 的數構成的數列。我們把一段旋律稱為 k,l 重複的,如果它滿足由乙個長度為l的字串重複了k次組成。如旋律abaabaabaaba是 4,3 重複的,因為它由aba重複4次組成。小hi想知道一部作品中k最大的 k,l 重複旋律。輸入一行乙個僅包含小寫字母的字串。字...
Hiho 123 字尾陣列四 重複旋律4
首先列舉 k,l 中的這個l,再列舉起始位置i,計算suffix i 和suffix i l 的lcp,記作lcp l,i 那麼k l,i 就等於lcp l,i l 1。對於所有的迴圈節長度l和起始位置i,最大的k l,i 就是答案。using system namespace hiho stati...