hiho一下第128周 后缀自动机二·重复旋律5
#1445 : 后綴自動(dòng)機(jī)二·重復(fù)旋律5
時(shí)間限制:10000ms 單點(diǎn)時(shí)限:2000ms 內(nèi)存限制:512MB描述
小Hi平時(shí)的一大興趣愛(ài)好就是演奏鋼琴。我們知道一個(gè)音樂(lè)旋律被表示為一段數(shù)構(gòu)成的數(shù)列。
現(xiàn)在小Hi想知道一部作品中出現(xiàn)了多少不同的旋律?
解題方法提示
輸入
共一行,包含一個(gè)由小寫(xiě)字母構(gòu)成的字符串。字符串長(zhǎng)度不超過(guò) 1000000。
輸出
一行一個(gè)整數(shù),表示答案。
樣例輸入解題方法提示
小Hi:本周的題目其實(shí)就是給定一個(gè)字符串S,要求出S的所有不同子串的數(shù)目。小Ho你知道如何快速求解么?
小Ho:我們最近在討論后綴自動(dòng)機(jī),所以肯定是和后綴自動(dòng)機(jī)有關(guān)!根據(jù)上周學(xué)習(xí)的SAM的基本概念和性質(zhì),SAM的每個(gè)狀態(tài)st都包含了一部分S的子串,記作substrings(st),并且(1)對(duì)于兩個(gè)不同狀態(tài)u和v,包含的子串substrings(u) ∩ substrings(v) = ?; (2)每個(gè)子串都恰好被一個(gè)狀態(tài)包含。所以我們只要構(gòu)造出來(lái)S對(duì)應(yīng)的SAM,再對(duì)所有狀態(tài)st求Σ(maxlen(st)-minlen(st))就是子串的數(shù)目。
小Hi:沒(méi)錯(cuò)。上周我們提到SAM有O(length(S))的構(gòu)造法。這周我們就來(lái)講一講如何構(gòu)造。
小Hi:首先,為了實(shí)現(xiàn)O(length(S))的構(gòu)造,我們對(duì)于每個(gè)狀態(tài)不能保存太多數(shù)據(jù)。例如substring(st)肯定是沒(méi)法保存下來(lái)了。對(duì)于狀態(tài)st我們只保存如下數(shù)據(jù):
| maxlen[st] | st包含的最長(zhǎng)子串的長(zhǎng)度 |
| minlen[st] | st包含的最短字串的長(zhǎng)度 |
| trans[st][] | st的轉(zhuǎn)移函數(shù) |
| slink[st] | st的Suffix Link |
小Hi:其次,我們用增量法構(gòu)造S對(duì)應(yīng)的SAM。我們從初始狀態(tài)開(kāi)始,每次添加一個(gè)字符S[1], S[2], ... S[N],依次構(gòu)造可以識(shí)別S[1], S[1..2], S[1..3], ... S[1..N]=S的SAM。
小Hi:假設(shè)我們已經(jīng)構(gòu)造好了S[1..i]的SAM。這時(shí)我們要添加字符S[i+1],于是我們新增了i+1個(gè)S[i+1]的后綴要識(shí)別:S[1..i+1], S[2..i+1], ... S[i..i+1], S[i+1]。 考慮到這些新增狀態(tài)分別是從S[1..i], S[2..i], S[3..i], ... , S[i], ""(空串)通過(guò)字符S[i+1]轉(zhuǎn)移過(guò)來(lái)的,所以我們還要對(duì)S[1..i], S[2..i], S[3..i], ... , S[i], ""(空串)對(duì)應(yīng)的狀態(tài)們?cè)黾酉鄳?yīng)的轉(zhuǎn)移。
小Hi:我們假設(shè)S[1..i]對(duì)應(yīng)的狀態(tài)是u,等價(jià)于S[1..i]∈ substrings(u)。根據(jù)上周的討論我們知道S[1..i], S[2..i], S[3..i], ... , S[i], ""(空串)對(duì)應(yīng)的狀態(tài)們恰好就是從u到初始狀態(tài)S的由Suffix Link連接起來(lái)路徑上的所有狀態(tài),不妨稱這條路徑(上所有狀態(tài)集合)是suffix-path(u->S)。
小Hi:顯然至少S[1..i+1]這個(gè)子串不能被以前的SAM識(shí)別,所以我們至少需要添加一個(gè)狀態(tài)z,z至少包含S[1..i+1]這個(gè)子串。
小Hi:首先考慮一種最簡(jiǎn)單的情況:對(duì)于suffix-path(u->S)的任意狀態(tài)v,都有trans[v][S[i+1]]=NULL。這時(shí)我們只要令trans[v][S[i+1]]=z,并且令slink[st]=S即可。
小Hi:例如我們已經(jīng)得到"aa"的SAM,現(xiàn)在希望構(gòu)造"aab"的SAM。如下圖所示:
小Hi:此時(shí)u=2,z=3,suffix-path(u->S)是桔色狀態(tài)組成的路徑2-1-S。并且這3個(gè)狀態(tài)都沒(méi)有對(duì)應(yīng)字符b的轉(zhuǎn)移。所以我們只要添加紅色轉(zhuǎn)移trans[2][b]=trans[1][b]=trans[S][b]=z即可。當(dāng)然也不要忘了slink[3]=S。
小Ho:那要是suffix-path(u->S)上有一個(gè)節(jié)點(diǎn)v,使得trans[v][S[i+1]]!=NULL怎么辦?
小Hi:好問(wèn)題。我們以下圖為例,假設(shè)我們已經(jīng)構(gòu)造"aabb"的SAM如圖,現(xiàn)在我們要增加一個(gè)字符a構(gòu)造"aabba"的SAM。
小Hi:這時(shí)u=4,z=6,suffix-path(u->S)是桔色狀態(tài)組成的路徑4-5-S。對(duì)于狀態(tài)4和狀態(tài)5,由于它們都沒(méi)有對(duì)應(yīng)字符a的轉(zhuǎn)移,所以我們只要添加紅色轉(zhuǎn)移trans[4][a]=trans[5][a]=z=6即可。面對(duì)S時(shí)我們遇到了小Ho你提出的問(wèn)題,trans[S][a]=1已經(jīng)存在,怎么辦?
小Ho:怎么辦呢?
小Hi:不失一般性,我們可以認(rèn)為在suffix-path(u->S)遇到的第一個(gè)狀態(tài)v滿足trans[v][S[i+1]]=x。這時(shí)我們需要討論x包含的子串的情況。如果x中包含的最長(zhǎng)子串就是v中包含的最長(zhǎng)子串接上字符S[i+1],等價(jià)于maxlen(v)+1=maxlen(x),比如在上面的例子里,v=S, x=1,longest(v)是空串,longest(1)="a"就是longest(v)+'a'。這種情況比較簡(jiǎn)單,我們只要增加slink[z]=x即可。
小Hi:如果x中包含的最長(zhǎng)子串 不是 v中包含的最長(zhǎng)子串接上字符S[i+1],等價(jià)于maxlen(v)+1 < maxlen(x),這種情況最為復(fù)雜,不失一般性,我們用下圖表示這種情況,這時(shí)增加的字符是c,狀態(tài)是z。
小Hi:在suffix-path(u->S)這條路徑上,從u開(kāi)始有一部分連續(xù)的狀態(tài)滿足trans[u..][c]=NULL,對(duì)于這部分狀態(tài)我們只需增加trans[u..][c]=z。緊接著有一部分連續(xù)的狀態(tài)v..w滿足trans[v..w][c]=x,并且longest(v)+c不等于longest(x)。這時(shí)我們需要從x拆分出新的狀態(tài)y,并且把原來(lái)x中長(zhǎng)度小于等于longest(v)+c的子串分給y,其余字串留給x。同時(shí)令trans[v..w][c]=y,slink[y]=slink[x], slink[x]=slink[z]=y。
小Ho:好像比較復(fù)雜。
小Hi:我們來(lái)舉個(gè)例子。假設(shè)我們已經(jīng)構(gòu)造"aab"的SAM如圖,現(xiàn)在我們要增加一個(gè)字符b構(gòu)造"aabb"的SAM。
小Hi:當(dāng)我們處理在suffix-path(u->S)上的狀態(tài)S時(shí),遇到trans[S][b]=3。并且longest(3)="aab",longest(S)+'b'="b",兩者不相等。其實(shí)不相等意味增加了新字符后endpos("aab")已經(jīng)不等于endpos("b"),勢(shì)必這兩個(gè)子串不能同屬一個(gè)狀態(tài)3。這時(shí)我們就要從3中新拆分出一個(gè)狀態(tài)5,把"b"及其后綴分給5,其余的子串留給3。同時(shí)令trans[S][c]=5, slink[5]=slink[3]=S, slink[3]=slink[6]=5。
小Hi:整個(gè)過(guò)程的代碼如下,其中狀態(tài)0代表初始狀態(tài)S;狀態(tài)u, v, x, y, z的意義如上文所述;-1代表slink或者trans不存在。
const int MAXL = 1000000; string s; int n = 0, len, st; int maxlen[2 * MAXL + 10], minlen[2 * MAXL + 10], trans[2 * MAXL + 10][26], slink[2 * MAXL + 10];int new_state(int _maxlen, int _minlen, int* _trans, int _slink) {maxlen[n] = _maxlen;minlen[n] = _minlen;for(int i = 0; i < 26; i++) {if(_trans == NULL)trans[n][i] = -1;elsetrans[n][i] = _trans[i];}slink[n] = _slink;return n++; }int add_char(char ch, int u) {int c = ch - 'a';int z = new_state(maxlen[u] + 1, -1, NULL, -1);int v = u;while(v != -1 && trans[v][c] == -1) {trans[v][c] = z;v = slink[v];}if(v == -1) { //最簡(jiǎn)單的情況,suffix-path(u->S)上都沒(méi)有對(duì)應(yīng)字符ch的轉(zhuǎn)移minlen[z] = 1;slink[z] = 0;return z;}int x = trans[v][c];if(maxlen[v] + 1 == maxlen[x]) { //較簡(jiǎn)單的情況,不用拆分xminlen[z] = maxlen[x] + 1;slink[z] = x;return z;}int y = new_state(maxlen[v] + 1, -1, trans[x], slink[x]); //最復(fù)雜的情況,拆分xslink[y] = slink[x];minlen[x] = maxlen[y] + 1;slink[x] = y;minlen[z] = maxlen[y] + 1;slink[z] = y;int w = v;while(w != -1 && trans[w][c] == x) {trans[w][c] = y;w = slink[w];}minlen[y] = maxlen[slink[y]] + 1;return z; }小Ho:咦?程序倒是意外的簡(jiǎn)單。
#include <iostream> #include <cstring> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <time.h> #include <string> #include <map> #include <stack> #include <vector> #include <set> #include <queue> using namespace std; typedef long long ll; const int inf=0x3f3f3f3f; const int N=1e6+100; const int M=1e6+5; int tot,slink[2*N],trans[2*N][26],minlen[2*N],maxlen[2*N]; char str[N]; int n; int newstate(int _maxlen,int _minlen,int* _trans,int _slink) {maxlen[++tot]=_maxlen;minlen[tot]=_minlen;slink[tot]=_slink;if(_trans)for(int i=0; i<26; i++)trans[tot][i]=_trans[i];return tot; } int add_char(char ch,int u) {int c=ch-'a',v=u;int z=newstate(maxlen[u]+1,-1,NULL,0);while(v&&!trans[v][c]) {trans[v][c]=z;v=slink[v];}if(!v) {minlen[z]=1;slink[z]=1;return z;}int x=trans[v][c];if(maxlen[v]+1==maxlen[x]) {slink[z]=x;minlen[z]=maxlen[x]+1;return z;}int y=newstate(maxlen[v]+1,-1,trans[x],slink[x]);slink[z]=slink[x]=y;minlen[x]=minlen[z]=maxlen[y]+1;while(v&&trans[v][c]==x) {trans[v][c]=y;v=slink[v];}minlen[y]=maxlen[slink[y]]+1;return z; } int main() {scanf("%s",str);int len=strlen(str),pre=1;tot=1;for(int i=0; i<len; i++) {pre=add_char(str[i],pre);}long long ans=0;for(int i=2; i<=tot; i++) {ans+=maxlen[i]-minlen[i]+1;}cout<<ans<<endl;return 0; }?
轉(zhuǎn)載于:https://www.cnblogs.com/jianrenfang/p/6341455.html
總結(jié)
以上是生活随笔為你收集整理的hiho一下第128周 后缀自动机二·重复旋律5的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Mac上关于shell使用Python3
- 下一篇: NIO中Selector分析