1. 程式人生 > >BZOJ.3998.[TJOI2015]弦論(後綴自動機)

BZOJ.3998.[TJOI2015]弦論(後綴自動機)

[1] size clu php scan 合並 如果 inline int

題目鏈接

\(Description\)

給定字符串S,求其第K小子串。(若T=0,不同位置的相同子串算1個;否則算作多個)

\(Solution\)

建SAM,處理出對於每個節點,它和它的所有後繼包含的子串數量sz(自葉子向根枚舉轉移更新即可),然後在SAM上走。
每次優先看字典序小的邊(設會到達v),若sz[v]<K,則K-=sz[v],枚舉下一條邊;否則K-=A[v],輸出這個轉移,然後p=v。(是A[v]!是匹配了v節點)
如果T=0,更新時sz[p]的初值為1,A[p]=1;如果T=1,那麽更新時sz[p]的初值為|right[p]|,A[p]=|right[p]|。
right的求法:按原串在SAM上走一遍,更新經過點的right,然後自parent樹底向上合並給fa的right就可以了。

感覺理解有個誤區。。雖然一個節點是會代表多個串,但是。。你從一個狀態走來並不是說匹配了這個點代表的所有串。所以就sz[]=1 or |right|。以後再匹配上別的點自然會加。
重新想了下好像之前理解的沒錯。。→_→
每個狀態s代表的所有串在原串中的出現次數和每次出現的右端點相同。

表示很服Rank1.
看了下他的SAM代碼,
算了學不來。。

//126308kb  5512ms
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N=1e6+3;

struct Suffix_Automaton
{
    int
T,K,L,las,tot,fa[N],son[N][26],len[N],sz[N],right[N],A[N],tm[N]; char s[N>>1]; void Insert(int c) { int p=las,np=++tot; len[las=np]=len[p]+1; for(; p&&!son[p][c]; p=fa[p]) son[p][c]=np; if(!p) fa[np]=1; else { int q=son[p][c]; if
(len[q]==len[p]+1) fa[np]=q; else { int nq=++tot; len[nq]=len[p]+1; memcpy(son[nq],son[q],sizeof son[q]); fa[nq]=fa[q], fa[q]=fa[np]=nq; for(; son[p][c]==q; p=fa[p]) son[p][c]=nq; } } } void Build() { scanf("%s",s), las=tot=1, L=strlen(s); for(int i=0; i<L; ++i) Insert(s[i]-'a'); for(int i=1; i<=tot; ++i) ++tm[len[i]]; for(int i=1; i<=L; ++i) tm[i]+=tm[i-1]; for(int i=1; i<=tot; ++i) A[tm[len[i]]--]=i; } void Query() { scanf("%d%d",&T,&K); if(!T) for(int i=1; i<=tot; ++i) sz[i]=right[i]=1; else{ for(int p=1,i=0; i<L; ++i) ++right[p=son[p][s[i]-'a']]; for(int i=tot,x=A[i]; i; x=A[--i]) right[fa[x]]+=right[x];//x not i! for(int i=1; i<=tot; ++i) sz[i]=right[i]; } // sz[0]=sz[1]=0; for(int i=tot,x=A[i]; i; x=A[--i]) for(int j=0; j<26; ++j) sz[x]+=sz[son[x][j]]; // sz[1]=0; int p=1; while(K>0) { for(int i=0; i<26; ++i) if(son[p][i])//... if(sz[son[p][i]]<K) K-=sz[son[p][i]]; else{ putchar(i+'a'), K-=right[p=son[p][i]]; break; } } } }sam; int main() { sam.Build(), sam.Query(); return 0; }

BZOJ.3998.[TJOI2015]弦論(後綴自動機)