又開坑?
**看不懂,網上資料又過於簡單?……
反正還是不懂。找到幾個資料,雲說好好看**最好,但是真的看不懂(zzs:我不知道你們看不看得懂clj的課件,因為我看不懂),子陵小孩子也是直接背,kpm大神直接看網上就懂其精髓(orz!!)。還是再去看**吧……&
2015.02.18 0:40 在雲的幫助下看完了……只能稱麗潔姐太神了,明天看能不能整理下……
字尾自動機
學了字尾陣列之後,寫出了個比較快的版本,然後又聽到kpm大神虐完字尾陣列又去虐sam了。於是終於在某天決定開坑。(隨便坑了kpm大神乙個中午和雲一天)。
雲這樣評價clj的**:至今能找到的最最科學的東西了
所以我還是按照**說說乙個蒟蒻的理解吧
也就是乙個能識別字串s的字尾的自動機。當且僅當sam(x)=true是字串x是s的字尾(同時sam也可以識別s的所有的字串哦!)
sam就是建立乙個最簡狀態字尾自動機。
定義了很多東西:
令st(str)表示trans(init,str)。就是初始狀態開始讀入字串str之後,能到達的狀態。
令reg(a)表示自動機a能識別的字元。由於我們現在建的是字串s的字尾自動機,所以reg(a)就是s的所有字尾。(或者這樣表示reg(st(s))就是s的所有字尾)
首先如果某個字串x是s的字串,那麼在這個字串x後加上一些字元後就有可能成為s的字尾,而且有可能有不同種方法使它成為s的字尾,也就是說它可能是s的多個字尾的共同字首!
比如說我們現在建立起s的sam了。然後讀入了字串s1,那麼現在sam就走到st(s1),那麼x如果可以被st(s1)識別,那麼x必須是s的字尾,現在走到了st(s1)可以識別x,那麼s1+x也是s的字尾。那麼可以說乙個狀態st(s1)可以識別的一定是s的字尾。
現在我們來舉個看個例子。
s=abbbabbabbbbbabba(直接用**)
其中bba出現的位置有
bbabbabbbbbabba
bbabbbbbabba
bbabba
bba也就是bba一共出現在
我們記右端點為right,也就是right(bba)=
然後我們發現有些時候有些字串的right集合一樣的,那我們乾脆就記在一起,即有個狀態f表示所有right相同的字串的集合,這個集合就記為right(f)吧。
然後字串的出現的區間中無論長度如何,從ri往前數不超過li所得到的字串一定是一樣的,比如上面的bba=,那麼6,9,15,18往前數兩個字元都是ba。也就是給個right集合,然後再個適合的長度,就可以確定乙個字串了。
所有right集合相同的字串構成乙個狀態f,那麼對於乙個狀態,也比如存在乙個長度範圍,【l,r】,這個範圍內從ri外前數多上個字元所構成的字串的right集合還是right(f)。我們記為【min(f),max(f)】。
現在有兩個狀態a和b,他們的right集合就分別叫ra和rb吧。
如果ra和rb有交集,即其中有相同的ri,那麼他們的適合長度區間一定不會有交集(否則某個字串的right即等於ra又等於rb但是ra<>rb,顯然不可能)。但是ra和rb又有交集啊?所以,只能是這樣——乙個是另乙個的真子集。比如上面的bba和bbba(兩個屬於不同的狀態),可以發現bbba的right是bba的right的子集,同時bba是bbba的字尾。so,「個串的right集合,要麼不相交,要麼乙個是另乙個的真子集。」
結果可以發現right集合其實構成了乙個樹(大集合的兒子們是小集合)。而且還可以發現兒子的min一定是爸爸的max+1(如果不是那爸爸的max+1才是兒子min,矛盾嘛)。
p.s 子串的性質。
首先每個子串都有屬於某個狀態,求這個子串出現的個數就變成了求這個子串所在狀態的right集合的大小。但是維護這個集合的大小有點麻煩,我們想到right集合是個樹形結構,right集合就是它的兒子們right的和,而兒子們的right又是孫子們right的和……最後就是right集合就是它的子樹中所有葉子節點的right的和,那麼直接用dfs序統計不就好了?!
怎麼做乙隻sam?
我們先來看狀態間的轉移:乙個狀態a,它的right(a)=,然後它可以沿一條標號為c的邊轉移到狀態b,那麼right(b)必須是right(a)中那些s[ri]=c的ri+1組成的集合。即right(b)=。
然後如果a有一條標號為c的邊,那麼他在樹中的父親f也一定有一條標號為c的邊。比如a表示aa,f表示a,那麼a有一條編號為c的到它的兒子,那麼f中可能有一條c到f的兒子(ac是aac的子串嘛)。
現在來想怎麼做。
令當前字串為t,新字元為x,令t的長度為l。
現在用sam(t)構造sam(tx)。新增了一些字元,這些字串都是tx的字尾,而tx的字尾,就是t的字尾後面加乙個x。
記v1,v2,v3,。。。,vk,v為right集合中包括l的節點,然後這些v必然是從曾孫子到曾祖父,也就是從最小的v1,(right(v1)=)開始到root的整條路徑。
設加入x後新的節點為np,即st(tx)=np,然後right(np)=(這些都是顯然的)。
舉個例子:
如果當前要加入x,然後第乙個x的邊為vp(也就是aaaaa所在狀態),然後q就是aaaaax所在狀態,right(q)==,長度是【1,7】。如果讓q直接插入l+1,那麼right(q)=,但是長度變成了【1,6】。所以不能直接插入。
aaaaaaxaaaaaaaaaaaaaaxaaaaaaaabaaaaax
aaaaaaxaaaaaaaaaaaaaaxaaaaaaaabaaaaax
顯然如果max(q)=max(vp)+1就沒有任何問題啦。
解釋?:(霧)直接插會讓長度變短,但是無論多短,都要》=max(vp)+1,如果原來就是max(q)=max(vp)+1,就不用擔心長度變短的影響了……
但是max(q)>max(vp)+1怎麼破?
接下來考慮節點nq,在轉移的過程中,結束位置l+1是不起作用的,所以trans(nq)就跟原來的trans(q)是一樣的,拷貝即可。
(後面覺得麗潔姐講的挺好的,其實是我太弱了似乎又理解得不那麼透徹?)
procedureview codeadd(x:longint);
vari,new,now,old,more:longint;
begin
new:=addpoint;
now:=last;
step[new]:=step[now]+1;
while (now>=0) and (son[now,x]=-1) do
begin
son[now,x]:=new;
now:=pre[now];
end; last:=new;
if now<0
then pre[new]:=0
else
if step[son[now,x]]=step[now]+1
then
pre[new]:=son[now,x]
else
begin
old:=son[now,x];
more:=addpoint;
for i:=0
to25
do son[more,i]:=son[old,i];
step[more]:=step[now]+1
; pre[more]:=pre[old];
pre[old]:=more;
pre[new]:=more;
while (now>=0) and (son[now,x]=old) do
begin
son[now,x]:=more;
now:=pre[now];
end;
end;
end;
(就不要吐槽**醜了)
字尾自動機
基礎知識 step i 表示的是字串i在原字串中的位置。pareint i 表示root到parent i 的子串是root到i的最長字尾。字尾自動機遍歷可以得到原字串的所有子串。特殊技巧 一 字尾自動機的不同子串數有兩種求法 1.ans step i step parent i 1 i cnt 2...
字尾自動機
常用於處理字串問題,可以高效解決許多字串問題。有點像將乙個字串的所有字尾都建在乙個ac自動機上,但不同的是字尾自動機的節點數最多為2 n,因為它只記錄需要記錄的點,一些沒有記錄東西的點可以視為與下面有價值的節點並在一起,這樣大大降低了時間複雜度和空間複雜度。對於每乙個節點記錄它的後面加上每個字元後字...
字尾自動機
基礎學習 簡潔明瞭的講解 總狀態數不超過2n 12n 1 2n 1 包括初始狀態 統計每個end po sendpos endpos 等價類出現位置數量時,要按長度從長到短的計算cnt cntcn t。那為什麼一定要從長到短呢?比如回文自動機就直接是按照節點編號從大到小計算cnt cntcn t 罪...