【后缀自动机】hihocoder1445 后缀自动机二·重复旋律5
解題方法提示
小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ù):
| 數(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)開始,每次添加一個(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開始有一部分連續(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不存在。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long ll;
#define MAXL 1000000
#define MAXC 26
char s[MAXL+10];//文本串
int len/*文本串長(zhǎng)度*/;
struct SAM{
int n/*狀態(tài)數(shù)0~n-1*/,maxlen[2*MAXL+10],minlen[2*MAXL+10],trans[2*MAXL+10][MAXC],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<MAXC;++i){
if(_trans==NULL){
trans[n][i]=-1;
}
else{
trans[n][i]=_trans[i];
}
}
slink[n]=_slink;
return n++;
}
int add_char(char ch,int u,int pos){
if(u==-1){
return new_state(0,0,NULL,-1);
}
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)單的情況,不用拆分x
minlen[z]=maxlen[x]+1;
slink[z]=x;
return z;
}
int y=new_state(maxlen[v]+1,-1,trans[x],slink[x]);//最復(fù)雜的情況,拆分x
slink[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;
}
}sam;
int m;
ll ans;
int main(){
// freopen("hihocoder1445.in","r",stdin);
// freopen("hihocoder1445.out","w",stdout);
char T[MAXL+10];
scanf("%s",s);
len=strlen(s);
int U=sam.add_char(0,-1,0);
for(int i=0;i<len;++i){
U=sam.add_char(s[i],U,i);
}
for(int i=1;i<sam.n;++i){
ans+=(ll)(sam.maxlen[i]-sam.minlen[i]+1);
}
cout<<ans<<endl;
return 0;
}
總結(jié)
以上是生活随笔為你收集整理的【后缀自动机】hihocoder1445 后缀自动机二·重复旋律5的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: [转]在NopCommerce中新增一个
- 下一篇: ecshop 网站标题不更新或内容不更新