日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

SAM学习小记

發布時間:2023/12/3 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SAM学习小记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

只是一個小記,不是算法詳解


參考資料

史上最通俗的后綴自動機詳解
廣義SAM模板題解


正題


概念

定義

簡單的,一個有向無環圖,邊有字母,滿足起點開始的每一條路徑都是原串的一個子串。
并且保證復雜度在O(n)O(n)O(n)級別內的。

endposendposendpos相關

每一個子串pppendpos(p)endpos(p)endpos(p)被定義為原串中所有出現子串ppp的末尾位置集合。
endposendposendpos相同的子串被定義為一個endposendposendpos類,可以證明任何一個串的endposendposendpos類數量不會超過O(n)O(n)O(n)級別。
性質:

  • 對于兩個endposendposendpos相同的子串,其中短的一個一定是另一個的后綴
  • 對于任意兩個子串p,qp,qp,q,且qqq的長度大于ppp,一定有endpos(p)?endpos(q)endpos(p)\subseteq endpos(q)endpos(p)?endpos(q)endpos(p)∩endpos(q)=?endpos(p)\cap endpos(q)=\varnothingendpos(p)endpos(q)=?

parentsparentsparents

根據endposendposendpos的兩個性質我們可以構建一棵樹,其中對于兩個endposendposendposx?yx\subseteq yx?y那么就有一條邊x?>yx->yx?>y
性質:

  • 對于一個在faxfa_xfax?endposendposendpos類里的串一定是xxxendposendposendpos類串的后綴
  • 定義在faxfa_xfax?endposendposendpos類里的最長的串長度為LenLenLen,在endposendposendpos類里最短的串長度為lenlenlen那么有Len=len+1Len=len+1Len=len+1

還有一個最重要性質,就是parentsparentsparents里的節點就是SAMSAMSAM里的節點


代碼解析

懶得搞圖,圖片去看參考資料。

void add(int c){int p=las;int np=las=++cnt;len[np]=len[p]+1;siz[cnt]++;for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;if(!p)fa[np]=1;else{int q=ch[p][c];if(len[p]+1==len[q])fa[np]=q;else{int nq=++cnt;len[nq]=len[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[nq]));fa[nq]=fa[q];fa[np]=fa[q]=nq;for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;}}return; }
  • 333行:新節點的最長一定新串的長度,因為左右不可能再加任何節點。
  • 444行:往前跳,中途指一條ccc的邊向新節點npnpnp(更新路徑上的endposendposendpos)。但是如果一個位置已經有這條邊了,前面已經加入過這個字符即有這些新的endposendposendpos了。在parentparentparent樹上跳的原因是因為這條鏈上的endposendposendpos是包含后綴的。
  • 555行:如果字符ccc是新出現的字符,那么這一定是一個由根節點印出來的新endposendposendpos類。
  • 888行:定義新的pppp′p'p,因為p′p'pppp的祖先,且p′p'p中最長的長度加一是ppp中最長的,那么也就是qqq中最長的與npnpnp擁有相同的endposendposendposendpos(np)?endpos(q)endpos(np)\subseteq endpos(q)endpos(np)?endpos(q)那么npnpnp的父節點就是qqq
  • 10~1310\sim 131013行:這是SAMSAMSAM中最關鍵的部分,當lenp+1len_p+1lenp?+1不等于lenqlen_qlenq?時,那么表示lenqlen_qlenq?中不能所有的endposendposendpos都加入nnn,而是有一部分要分離出來。此時我們需要新建立一個節點nqnqnq來儲存加入了nnn這個endposendposendposendposendposendpos類。首先加入新字符之前nqnqnqqqq完全相同,所以我們可以直接繼承qqq的信息。而因為nqnqnq是可以加入nnn這一信息的,所以有endpos(q)?endpos(nq)endpos(q)\subseteq endpos(nq)endpos(q)?endpos(nq)所以我們讓qqq的父節點為nqnqnq,然后顯然npnpnp的父節點也為nqnqnq。然后我們要往前更新信息,如果跳到chp,c≠qch_{p,c}\neq qchp,c??=q時,這個節點ppp本身ccc的出邊時本身就與qqq沒有關系的,所以就可以不用再更新了。

廣義SAM

如果一個SAMSAMSAM中我們要加入多個字符串時我們應該怎么辦,廣義SAMSAMSAM分為離線的和在線的做法。

離線做法:對于所有的字符串我們先構造出一棵TrieTrieTrie樹,然后每一次的lastlastlast就是它TrieTrieTrie樹上的父節點的節點,因為這樣保證了一個節點后面不會插入重復的字符。
在線做法:考慮如果插入了重復的字符怎么辦,那么證明這個節點的endposendposendpos類一定是有出現過的,我們可以像SAMSAMSAM中后面那個判斷一樣搞就好了
這里是在線做法的代碼:

ll Ins(ll c,ll last){ll p=last;if(ch[p][c]){ll q=ch[p][c];if(len[p]+1==len[q])return q;else{ll nq=++cnt;len[nq]=len[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[nq]));fa[nq]=fa[q];fa[q]=nq;for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;return nq;}}ll np=++cnt;len[np]=len[p]+1;for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np;if(!p)fa[np]=1;else{ll q=ch[p][c];if(len[p]+1==len[q])fa[np]=q;else{ll nq=++cnt;len[nq]=len[p]+1;memcpy(ch[nq],ch[q],sizeof(ch[nq]));fa[nq]=fa[q];fa[q]=fa[np]=nq;for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=nq;}}return np; }

例題解析

P3804-[模板]后綴自動機

給出一個長度為nnn的串,求一個最大的子串長度乘上子串出現次數

在構造SAMSAMSAM的時候統計每個endposendposendpos類包含了幾個endposendposendpos,這就是這個endposendposendpos類子串中出現的次數,然后每個endposendposendpos類取最長的子串就好了(在構造時已經保存了)。
對于每個endposendposendpos的集合如何統計,對于每個npnpnp,它都會有一個前綴iii作為這個endposendposendpos類,而在往下的過程中這個前綴位置iii會丟失(因為再往下的末尾都是在iii之后,而沒有一個在iii的后綴是在之后加入的)。也就是讓fnp++f_{np}++fnp?++,然后按照長度從大到小讓ffax+=fxf_{fa_x}+=f_{x}ffax??+=fx?,因為一個節點的父節點長度一定比子節點小。

P3975-[TJOI2015]弦論

求一個字符串第kkk大的子串。

對于相同的子串算同一個時,那么其實就是求SAMSAMSAM上字典序第kkk大的路徑,處理出每一個節點的后繼狀態直接跑即可。
對于相同的子串不算同一個時,我們處理處每個節點的endposendposendpos集合大小,這就是這個endposendposendpos類中每個子串的出現次數。然后用這個處理后繼狀態直接跑即可。

SP1811-Longest Common Substring

求兩個串的最長公共子串

我們先構建第一個串的SAMSAMSAM,然后用第二個串在第一個串上跑,如果不能繼續跑下去那么我們直接在parentparentparent樹上往前跳就好了,因為在SAMSAMSAMparentparentparent樹的左右可以類似于ACACAC自動機中failfailfail樹的定義。然后跳完了之后取len+1len+1len+1就好了。

P5341-[TJOI2019]甲苯先生和大中鋒的字符串

求出現次數恰好為kkk的字符串中出現次數最多的長度

因為對于一個相同的endposendposendpos類中的字符串都是長度連續且不同的,也就是[lenfax+1,lenx][len_{fa_x}+1,len_x][lenfax??+1,lenx?]這個范圍內每一個長度各有一個字符串,然后差分做就好了。

總結

以上是生活随笔為你收集整理的SAM学习小记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。