廣義SAM
阿新 • • 發佈:2022-03-08
參考
還有首先你要會SAM吧~
用途
相比與單串SAM,廣義自動機能儲存的是多個字串。
有兩種寫法,第一種是離線利用trie樹結構,第二種是線上偽廣義SAM
離線+Trie
首先構建出trie樹。
然後在trie樹上BFS(),用\(pos[u]\)對映trie樹上\(u\)節點對應SAM上的節點。
為什麼不dfs,因為時間複雜度是trie樹上所有葉子到根的距離和,證明BFS\(O(n)\)複雜度具體見上面參考部落格。
code:
struct SAM { int tr[N<<1][M],nd,len[N<<1],par[N<<1],pos[N*M]; queue<int> Q; int Insert(int x,int lst) { ...略,返回新節點編號 } void BFS() { Q.push(1);pos[1]=1; while(!Q.empty()) { int u=Q.front(); Q.pop(); for(int i=0;i<26;i++) { int v=T.go[u][i]; if(!v)continue; pos[v]=Insert(i,pos[u]); Q.push(v); } } }S;
相信上面code的你能夠很好的理解
線上
雖然它很偽,但通常跑的比上面那種快,而且對於很多問題在上面處理起來很方便。
方便講解,先放一份原來插單串的code:
int Insert(int x,int lst) { int p=lst,np=++nd;len[np]=len[p]+1; for(;!tr[p][x];p=par[p])tr[p][x]=np; if(!p) {par[np]=1;} else { int q=tr[p][x]; if(len[q]==len[p]+1) {par[np]=q;} else { int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1; for(int j=0;j<26;j++)tr[nq][j]=tr[q][j]; par[q]=par[np]=nq; for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq; } } return np; }
多串總體的區別就是每加入一個新串前讓lst=1(root)
這樣會出現之前插單串沒出現過的情況:之前p=lst往上找tr[p][x]!=0之前肯定會存在tr[p][x]=0
然而現在可能一開始tr[lst][x]!=0,這有什麼問題嗎?就跟原來一樣分兩類討論(拆點或不拆點)
問題就在於,這時新加的np是個空點,因為它是完全沒有必要的。
令q=tr[lst][x]
- 如果len[q]=len[lst]+1,那此時直接返回q即可。
- 否則len[q]>len[lst]+1,還是要拆出nq,但是在(上面拆點)程式碼中唯一用到新點(np)的就是par[np]=nq
可len[nq]=len[np]的,nq已經包含了np了……
So Easy!直接不創np這個空點不就行了
我們上面也說明了現在新程式碼的寫法。先判斷tr[lst][x]!=0時就不定義新np點……最後return nq,否則寫法跟上面單串一樣的。 - code
struct SAM {
int tr[N<<1][M],nd,len[N<<1],par[N<<1];
queue<int> Q;
SAM() {nd=1;}
int Insert(int x,int lst) {
int p=lst;
if(tr[p][x]) {
int q=tr[p][x];
if(len[q]==len[p]+1)return q;
int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
for(int j=0;j<c;j++)tr[nq][j]=tr[q][j];
for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
return par[q]=nq;
}
int np=++nd;len[np]=len[p]+1;
for(;!tr[p][x];p=par[p])tr[p][x]=np;
if(!p) {par[np]=1;}
else {
int q=tr[p][x];
if(len[q]==len[p]+1) {par[np]=q;}
else {
int nq=++nd;par[nq]=par[q];len[nq]=len[p]+1;
for(int j=0;j<c;j++)tr[nq][j]=tr[q][j];
par[q]=par[np]=nq;
for(;tr[p][x]==q;p=par[p]) tr[p][x]=nq;
}
}
return np;
}
}S;