標籤:方法 min 修改 元素 mod aabb 最壞情況 0ms 疑問
尾碼自動機三·重複旋律6時間限制:15000ms單點時限:3000ms記憶體限制:512MB描述
小Hi平時的一大興趣愛好就是演奏鋼琴。我們知道一個音樂旋律被表示為一段數構成的數列。
現在小Hi想知道一部作品中所有長度為K的旋律中出現次數最多的旋律的出現次數。但是K不是固定的,小Hi想知道對於所有的K的答案。
解題方法提示
輸入
共一行,包含一個由小寫字母構成的字串S。字串長度不超過 1000000。
輸出
共Length(S)行,每行一個整數,表示答案。
-
範例輸入
-
aab
-
範例輸出
-
211
小Hi:上次我們已經學習了尾碼自動機了,今天我們再來解決一個用到尾碼自動機的問題。
小Ho:好!那我們開始吧!
小Hi:現在我們要對K=1..length(S)求出所有長度為K的子串中出現次數最多的子串的出現次數。小Ho你有什麼想法嗎?
小Ho:我有一個Naive的想法。在上上周我們已經知道對於SAM中的一個狀態st,endpos(st)就是st這個狀態包含的子串在S中的所有結束位置(st包含所有子串都具有相同的結束位置集合)。每個不同的結束位置就對應了一次出現次數。所以如果我們能在構造SAM的過程中把endpos也都求出來,應該就能解決這個問題了。
小Hi:你這個思路不錯,但是複雜度有些高。我們用|endpos(st)|表示endpos(st)的大小。那麼對於一個狀態st,|endpos(st)|最壞可能到達O(length(S))的層級,所有狀態的|endpos(st)|之和最壞也可能達到O(length(S)^2)層級。
小Hi:比如對於S="aaaaa",其狀態如下,容易發現Σ|endpos(st)| = 1 + 2 + 3 + ...。
| 狀態 |
子串 |
endpos |
| S |
空串 |
|
| 1 |
a |
{1,2,3,4,5} |
| 2 |
aa |
{2,3,4,5} |
| 3 |
aaa |
{3,4,5} |
| 4 |
aaaa |
{4,5} |
| 5 |
aaaaa |
{5} |
小Ho:所以如果我們對每個狀態維護endpos的話,複雜度至少也是O(length(S)^2)的哦?那我們能不能只維護endpos(st)的大小,即|endpos(st)|,而不維護具體的endpos(st)呢?就像我們在上周只維護了maxlen(st)和minlen(st),而不維護具體的substrings(st)。
小Hi:你這個想法也很不錯。可惜如果用上周的增量法建SAM時維護|endpos(st)|的話,額外的代價有點高。舉個例子,假設我們已經建好了S="aaaaa"的SAM:
| 狀態 |
子串 |
endpos |
|endpos| |
| S |
空串 |
|
|
| 1 |
a |
{1,2,3,4,5,6} |
5 |
| 2 |
aa |
{2,3,4,5,6} |
4 |
| 3 |
aaa |
{3,4,5,6} |
3 |
| 4 |
aaaa |
{4,5,6} |
2 |
| 5 |
aaaaa |
{5,6} |
1 |
當我們增加一個字元‘a‘,建立S="aaaaaa"的SAM:
| 狀態 |
子串 |
endpos |
|endpos| |
| S |
空串 |
|
|
| 1 |
a |
{1,2,3,4,5,6} |
6 |
| 2 |
aa |
{2,3,4,5,6} |
5 |
| 3 |
aaa |
{3,4,5,6} |
4 |
| 4 |
aaaa |
{4,5,6} |
3 |
| 5 |
aaaaa |
{5,6} |
2 |
| 6 |
aaaaaa |
{6} |
1 |
你會發現前面的狀態1-5都需要修改,它們的|endpos|都增加了1。
小Ho:所以我們如果維護|endpos(st)|的話,最壞情況下複雜度又會是O(length(S)^2)。那我們該怎麼辦呢?
小Hi:我們要換個思路,不追求在構造SAM的過程中同時把|endpos(st)|算出來。而是先構造SAM,再單獨把每個狀態的|endpos(st)|算一遍。還是以S="aabbabd"為例。
小Hi:這次我們不考慮Transition Function,只留下Suffix Links。此外,如果一個狀態能接受(也就是包含)S的某個首碼的話,我們就把這個狀態標記成綠色。例如狀態4包含"aabb",狀態7包含"aabbab"。
| 狀態 |
子串 |
endpos |
| S |
空串 |
{0,1,2,3,4,5,6} |
| 1 |
a |
{1,2,5} |
| 2 |
aa |
{2} |
| 3 |
aab |
{3} |
| 4 |
aabb,abb,bb |
{4} |
| 5 |
b |
{3,4,6} |
| 6 |
aabba,abba,bba,ba |
{5} |
| 7 |
aabbab,abbab,bbab,bab |
{6} |
| 8 |
ab |
{3,6} |
| 9 |
aabbabd,abbabd,bbabd,babd,abd,bd,d |
{7} |
小Hi:根據上上周基本概念中介紹的內容,我們知道Suffix Links把SAM中的所有狀態連成了一棵樹,並且父子(祖孫)之間的endpos集合有內含項目關聯性,非祖孫之間的endpos交集為空白集。(還記得這個定理嗎?對於S的兩個子串s1和s2,不妨設length(s1) <= length(s2),那麼 s1是s2的尾碼若且唯若endpos(s1) ⊇ endpos(s2),s1不是s2的尾碼若且唯若endpos(s1) ∩ endpos(s2) = ∅)。我們能不能"自底向上"求出所有狀態的|endpos(st)|呢?
小Ho:好像有點意思。你繼續講。
小Hi:我們從2個具體的例子入手來分析這個問題。第一個例子是狀態8,假設我們要求|endpos(8)|。我們知道狀態8有兩個兒子分別是狀態3和狀態7(即slink[7]=slink[3]=8),其中endpos(3)={3}, endpos(7)={6},這時|endpos(8)|是多少?
小Ho:看上去endpos(8)=endpos(3) ∪ endpos(7)。所以|endpos(8)| = |endpos(7)| + |endpos(3)|?
小Hi:我們再看一個例子,狀態1,假設我們要求|endpos(1)|。我們知道狀態1有兩個兒子分別是狀態2和狀態6,其中endpos(2)={2}, endpos(6)={5},這時|endpos(1)|是多少?
小Ho:endpos(1)是{1, 2, 5},並不是endpos(2) ∪ endpos(6) = {2, 5},多了一個元素1。
小Hi:通過這兩個例子你有什麼思路嗎?
小Ho:我們明白了。一個狀態st對應的|endpos(st)|至少是它兒子的endpos大小之和。這一點還是比較容易證明的。假設x和y是st的兩個兒子,那麼根據Suffix Link的定義,我們知道st中的子串都是x中子串的尾碼,也是y中子串的尾碼。所以endpos(st) ⊇ endpos(x) 並且 endpos(st) ⊇ endpos(y)。又根據Suffix Link的定義我們知道x中的子串肯定不是y中子串的尾碼,反之亦然,所以endpos(x) ∩ endpos(y) = ∅。所以|endpos(st)| >= |endpos(x)| + |endpos(y)|。
小Hi:那麼|endpos(st)|可能比st兒子的endpos大小之和大多少呢?
小Ho:最多就大1。並且大1的情況若且唯若st是上文提到的綠色狀態,即st包含S的某個首碼時才發生。我們分析endpos(1)={1, 2, 5}就會發現,它比endpos(2) ∪ endpos(6) = {2, 5}多出來的結束位置1的原因就是狀態1還包含S的長度為1的首碼"a"。更一般的情形是如果某個狀態st包含S的一個首碼S[1..l],那麼一定有l∈endpos(st),並且l不能從st的兒子中繼承過來。這時就需要+1。
小Hi:沒錯。那麼我們如何判斷哪些狀態應該標記成綠色狀態呢?
小Ho:可以在構造SAM的時候順手做了。回顧我們構造SAM的演算法,當新加入一個字元的時候,我們至少會建立一個狀態z(還可能建立一個狀態y),這個狀態z一定是綠色狀態(y一定不是)。
小Hi:沒錯,我們回顧一下。先構造SAM,順手把綠色狀態標記出來。然後再對Suffix Link連成的樹"自底向上"求出每一個狀態的|endpos(st)|,這一步"自底向上"可以通過拓撲排序完成,我們很早之前就講過,不再贅述。
小Ho:求出每一個狀態的|endpos(st)|後,我們還需要求出每個長度的子串最多出現了多少次。我對這一步還有疑問。假設ans[l]表示長度為l的子串最多出現的次數。我的想法是對於每個狀態st,都要迴圈一遍,利用|endpos(st)|更新ans[minlen(st)] ... ans[maxlen(st)]的值。這一步複雜度好像又是O(length(S)^2)的,這不是功虧一簣了嗎?我寫的虛擬碼如下。
FOREACH State st:FOR i = minlen(st) .. maxlen(st): ans[i] = max(ans[i], |endpos(st)|)
小Hi:你提的這個問題很好。這是我們最後要解決的一個問題了。值得注意的是ans[1], ans[2], ... ans[length(S)]一定是一個單調遞減序列。所以我們對於每個狀態st,只需要更新ans[maxlen(st)]。之後令i = length(S)-1 .. 1,從後向前掃描一遍,令ans[i] = max(ans[i], ans[i+1]),即可。虛擬碼如下,你仔細體會一下。
FOREACH State st: ans[maxlen(st)] = max(ans[maxlen(st)], |endpos(st)|)FOR i = length(S) - 1 .. 1: ans[i] = max(ans[i], ans[i+1])
小Hi講的實在太好了,就不說什麼了。
1 /************************************************************************* 2 > File: main.cpp 3 > Author: You Siki 4 > Mail: [email protected] 5 > Time: 2016年12月23日 星期五 15時14分18秒 6 ************************************************************************/ 7 8 #include<bits/stdc++.h> 9 10 //using namespace std; 11 12 const int maxn = 2000005; 13 14 /* AUTOMATON */ 15 16 int last = 1; 17 int tail = 2; 18 int fail[maxn]; 19 int step[maxn]; 20 int flag[maxn]; 21 int next[maxn][26]; 22 23 inline void buildAutomaton(char *s) 24 { 25 while (*s) 26 { 27 int p = last; 28 int t = tail++; 29 int c = *s++ - ‘a‘; 30 31 flag[t] = true; 32 step[t] = step[p] + 1; 33 34 while (p && !next[p][c]) 35 next[p][c] = t, p= fail[p]; 36 37 if (p) 38 { 39 int q = next[p][c]; 40 if (step[q] == step[p] + 1) 41 fail[t] = q; 42 else 43 { 44 int k = tail++; 45 fail[k] = fail[q]; 46 fail[q] = fail[t] = k; 47 step[k] = step[p] + 1; 48 for (int i = 0; i < 26; ++i) 49 next[k][i] = next[q][i]; 50 while (p && next[p][c] == q) 51 next[p][c] = k, p = fail[p]; 52 } 53 } 54 else 55 fail[t] = 1; 56 last = t; 57 } 58 } 59 60 int que[maxn]; 61 int cnt[maxn]; 62 int ans[maxn]; 63 64 inline int solveAndPrintAnswer(char *s) 65 { 66 int hd = 0, tl = 0, n = strlen(s); 67 68 for (int i = 1; i < tail; ++i) 69 ++cnt[fail[i]]; 70 71 for (int i = 1; i < tail; ++i) 72 if (!cnt[i])que[tl++] = i; 73 74 while (hd != tl) 75 { 76 int t = que[hd++]; 77 flag[fail[t]] += flag[t]; 78 if (--cnt[fail[t]] == 0) 79 que[tl++] = fail[t]; 80 } 81 82 for (int i = 1; i < tail; ++i) 83 if (ans[step[i]] < flag[i]) 84 ans[step[i]] = flag[i]; 85 86 for (int i = n; i; --i) 87 if (ans[i] < ans[i + 1]) 88 ans[i] = ans[i + 1]; 89 90 for (int i = 1; i <= n; ++i) 91 printf("%d\n", ans[i]); 92 } 93 94 /* MAIN FUNC */ 95 96 char str[maxn]; 97 98 signed main(void) 99 {100 scanf("%s", str);101 buildAutomaton(str);102 solveAndPrintAnswer(str);103 }
@Author: YouSiki
hihoCoder 尾碼自動機三·重複旋律6