hihoCoder 尾碼自動機三·重複旋律6

來源:互聯網
上載者:User

標籤:方法   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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.