文章目錄 后綴自動機 算法實現過程 模板 習題 洛谷后綴自動機模板題 品酒大會 [HEOI2015]最短不公共子串 字符串
蒟蒻寫這篇blogblog b l o g 主要是存一下,后綴自動機的詳細搭建過程,方便以后復習 具體的某些證明,為什么這么做,正確性劈里啪啦一大堆就不贅述了講解指路?
后綴自動機
后綴自動機上每一條到ii i 的路徑對應一個子串,整個自動機包含了字符串的所有子串
很多時候可以和后綴數組等價使用
endposendpos e n d p o s :一個子串ii i 在整個字符串中出現的位置 最后一個字符的下標 構成的集合 舉個栗子 abcbcdeabcabcbcdeabc a b c b c d e a b c ,(從00 0 開始標號) 子串abcabc a b c 對應的endposendpos e n d p o s 為{2,9}\{2,9\} { 2 , 9 } ,子串bcbc b c 的endposendpos e n d p o s 為{2,4,9}\{2,4,9\} { 2 , 4 , 9 }
后綴自動機的編號對應的就是endposendpos e n d p o s 完全相同 的所有子串 依舊是上面的粒子abcbcdeabcabcbcdeabc a b c b c d e a b c 子串bcbc b c 的endposendpos e n d p o s 為{2,4,9}\{2,4,9\} { 2 , 4 , 9 } ,子串cc c 的endposendpos e n d p o s 也為{2,4,9}\{2,4,9\} { 2 , 4 , 9 } 那么后綴自動機上對應的idid i d 編號既表示bcbc b c 子串,也表示cc c 子串
算法實現過程
e.g.1e.g.1 e . g . 1 ,構建abcdabcd a b c d 的后綴自動機 Ⅰ最初始狀態,僅有一個空根,last=1last=1 l a s t = 1 ,lastlast l a s t 表示后綴自動機的最后一個節點 Ⅱ 將′a′'a' ′ a ′ 扔進去,新建一個節點cnt=2cnt=2 c n t = 2 ,len=len[last]+1=1len=len[last]+1=1 l e n = l e n [ l a s t ] + 1 = 1 從lastlast l a s t 開始跳,發現11 1 沒有′a′'a' ′ a ′ 邊 則建立一條′a′'a' ′ a ′ 邊,并指向新點22 2 此時跳到了初始源點,22 2 的后綴鏈接只能指向11 1 ,lastlast l a s t 變為22 2 Ⅲ 將′b′'b' ′ b ′ 扔進去,新建一個節點cnt=3,len=len[last]+1=2cnt=3,len=len[last]+1=2 c n t = 3 , l e n = l e n [ l a s t ] + 1 = 2 從lastlast l a s t 開始跳后綴鏈接 22 2 沒有′b′'b' ′ b ′ 邊,新建一條并指向33 3 ,跳后綴鏈接到11 1 11 1 沒有′b′'b' ′ b ′ 邊,新建一條并指向33 3 此時已經到了根節點,33 3 的后綴鏈接只能指向11 1 ,last=3last=3 l a s t = 3 Ⅳ 將′c′'c' ′ c ′ 扔進去,新建一個節點cnt=4,len=3cnt=4,len=3 c n t = 4 , l e n = 3 從lastlast l a s t 開始跳后綴鏈接 33 3 沒有′c′'c' ′ c ′ 邊,新建一條并指向44 4 ,跳后綴鏈接到11 1 11 1 沒有′c′'c' ′ c ′ 邊,新建一條并指向44 4 此時已經到了根節點,44 4 的后綴鏈接只能指向11 1 ,last=4last=4 l a s t = 4 Ⅴ 將′d′'d' ′ d ′ 扔進去,新建一個節點cnt=5,len=4cnt=5,len=4 c n t = 5 , l e n = 4 從lastlast l a s t 開始跳后綴鏈接 44 4 沒有′c′'c' ′ c ′ 邊,新建一條并指向55 5 ,跳后綴鏈接到11 1 11 1 沒有′c′'c' ′ c ′ 邊,新建一條并指向55 5 此時已經到了根節點,55 5 的后綴鏈接只能指向11 1 ,last=5last=5 l a s t = 5 最簡單的一種后綴自動機就完成了 接下來就嘗試一下進階版
e.g.2e.g.2 e . g . 2 ,構建ababeababe a b a b e 的后綴自動機 Ⅰ先搭建空源點,last=1last=1 l a s t = 1 Ⅱ 加入′a′'a' ′ a ′ ,新建一個節點cnt=2,len[2]=len[last]+1=1cnt=2,len[2]=len[last]+1=1 c n t = 2 , l e n [ 2 ] = l e n [ l a s t ] + 1 = 1 11 1 沒有′c′'c' ′ c ′ 邊,新建一條并指向22 2 , 此時已經到了根節點,22 2 的后綴鏈接只能指向11 1 ,last=2last=2 l a s t = 2 Ⅲ 加入′b′'b' ′ b ′ ,新建一個節點cnt=3,len[3]=len[last]+1=2cnt=3,len[3]=len[last]+1=2 c n t = 3 , l e n [ 3 ] = l e n [ l a s t ] + 1 = 2 從lastlast l a s t 開始跳后綴鏈接 22 2 沒有′b′'b' ′ b ′ 邊,新建一條并指向33 3 ,跳后綴鏈接到11 1 11 1 沒有′b′'b' ′ b ′ 邊,新建一條并指向33 3 此時已經到了根節點,33 3 的后綴鏈接只能指向11 1 ,last=3last=3 l a s t = 3 Ⅳ 再加入′a′'a' ′ a ′ ,新建一個節點cnt=4,len[4]=len[last]+1=3cnt=4,len[4]=len[last]+1=3 c n t = 4 , l e n [ 4 ] = l e n [ l a s t ] + 1 = 3 從lastlast l a s t 開始跳后綴鏈接 33 3 沒有′a′'a' ′ a ′ 邊,新建一條并指向44 4 ,跳后綴鏈接到11 1 11 1 有一條指向22 2 的′a′'a' ′ a ′ 邊,滿足len[2]=len[1]+1len[2]=len[1]+1 l e n [ 2 ] = l e n [ 1 ] + 1 ,則直接將44 4 后綴鏈接指向22 2 結束,last=4last=4 l a s t = 4 Ⅴ 再加入′b′'b' ′ b ′ ,新建一個節點cnt=5,len[5]=len[last]+1=4cnt=5,len[5]=len[last]+1=4 c n t = 5 , l e n [ 5 ] = l e n [ l a s t ] + 1 = 4 從lastlast l a s t 開始跳后綴鏈接 44 4 沒有′b′'b' ′ b ′ 邊,新建一條并指向55 5 ,跳后綴鏈接到22 2 22 2 有一條指向33 3 的′b′'b' ′ b ′ 邊,滿足len[3]=len[2]+1len[3]=len[2]+1 l e n [ 3 ] = l e n [ 2 ] + 1 ,直接將55 5 后綴鏈接指向33 3 結束,last=5last=5 l a s t = 5 Ⅵ 加入新′c′'c' ′ c ′ ,新建一個節點cnt=6,len[6]=len[last]+1=5cnt=6,len[6]=len[last]+1=5 c n t = 6 , l e n [ 6 ] = l e n [ l a s t ] + 1 = 5 從lastlast l a s t 開始跳后綴鏈接 55 5 沒有′c′'c' ′ c ′ 邊,新建一條并指向66 6 ,跳后綴鏈接到33 3 33 3 沒有′c′'c' ′ c ′ 邊,新建一條并指向66 6 ,跳后綴鏈接到11 1 11 1 沒有′c′'c' ′ c ′ 邊,新建一條并指向66 6 此時已到根節點,66 6 只能鏈接11 1 ,last=6last=6 l a s t = 6 結束 這就是進階版了,沒有涉及到最終版的點復制 最后讓我們一起攜手走進最終版的后綴自動機構造
e.g.3e.g.3 e . g . 3 ,構建cababcabab c a b a b 的后綴自動機 Ⅰ 創造新源點,last=1,cnt=1last=1,cnt=1 l a s t = 1 , c n t = 1 Ⅱ 加入′c′'c' ′ c ′ ,新建一個節點cnt=2,len[2]=len[last]+1=1cnt=2,len[2]=len[last]+1=1 c n t = 2 , l e n [ 2 ] = l e n [ l a s t ] + 1 = 1 從lastlast l a s t 開始跳后綴鏈接 11 1 沒有′c′'c' ′ c ′ 邊,新建一條并指向22 2 此時已到根節點,22 2 只能鏈接1,last=21,last=2 1 , l a s t = 2 Ⅲ 加入′a′'a' ′ a ′ ,新建一個節點cnt=3,len[3]=len[last]+1=2cnt=3,len[3]=len[last]+1=2 c n t = 3 , l e n [ 3 ] = l e n [ l a s t ] + 1 = 2 從lastlast l a s t 開始跳后綴鏈接 22 2 沒有′a′'a' ′ a ′ 邊,新建一條并指向33 3 ,跳后綴鏈接到11 1 11 1 沒有′a′'a' ′ a ′ 邊,新建一條并指向33 3 此時已到根節點,33 3 只能鏈接1,last=31,last=3 1 , l a s t = 3 Ⅳ 加入′b′'b' ′ b ′ ,新建一個節點cnt=4,len[4]=len[last]+1=3cnt=4,len[4]=len[last]+1=3 c n t = 4 , l e n [ 4 ] = l e n [ l a s t ] + 1 = 3 從lastlast l a s t 開始跳后綴鏈接 33 3 沒有′b′'b' ′ b ′ 邊,新建一條并指向44 4 ,跳后綴鏈接到11 1 11 1 沒有′a′'a' ′ a ′ 邊,新建一條并指向44 4 此時已到根節點,44 4 只能鏈接1,last=41,last=4 1 , l a s t = 4 Ⅴ 加入′a′'a' ′ a ′ ,新建一個節點cnt=5,len[5]=len[last]+1=4cnt=5,len[5]=len[last]+1=4 c n t = 5 , l e n [ 5 ] = l e n [ l a s t ] + 1 = 4 從lastlast l a s t 開始跳后綴鏈接 44 4 沒有′a′'a' ′ a ′ 邊,新建一條并指向55 5 ,跳后綴鏈接到11 1 11 1 有′a′'a' ′ a ′ 邊,指向33 3 ,但是!!!len[3]≠len[1]+1len[3]≠len[1]+1 l e n [ 3 ] ? = l e n [ 1 ] + 1 ,不能像進階版直接鏈接,這里必須要點復制 新建一個33 3 的分身節點cnt=6cnt=6 c n t = 6 33 3 的所有信息(出入邊)除了原字符串間的邊(圖中黑色邊)全部修改為分點66 6 的邊,直接覆蓋 并且66 6 成為33 3 的直接后綴鏈接,替代11 1 len[6]=len[1]+1=1len[6]=len[1]+1=1 l e n [ 6 ] = l e n [ 1 ] + 1 = 1 相當于66 6 做了1,31,3 1 , 3 后綴鏈之間的承接點,保證了每一條邊上lenlen l e n 只會帶來+1+1 + 1 的影響 55 5 直接鏈接66 6 后結束,last=5last=5 l a s t = 5 Ⅵ 加入′b′'b' ′ b ′ ,新建節點cnt=7cnt=7 c n t = 7 從lastlast l a s t 開始跳后綴鏈接 55 5 沒有′b′'b' ′ b ′ 邊,新建一條指向77 7 ,跳后綴鏈接到66 6 66 6 有一條′b′'b' ′ b ′ 邊,指向44 4 ,判斷len[4]≠len[6]+1len[4]≠len[6]+1 l e n [ 4 ] ? = l e n [ 6 ] + 1 再次執行復制 操作 新建一個44 4 的分身節點cnt=8cnt=8 c n t = 8 44 4 的所有信息(出入邊)除了原字符串間的邊(圖中黑色邊)全部修改為分點88 8 的邊,直接進行覆蓋 88 8 成為44 4 的直接后綴鏈接,len[8]=len[6]+1=2len[8]=len[6]+1=2 l e n [ 8 ] = l e n [ 6 ] + 1 = 2 77 7 直接鏈接88 8 后結束,last=7last=7 l a s t = 7
? len[x]len[x] l e n [ x ] 復制點的lenlen l e n 不等于被復制點的原后綴鏈接的len+1len+1 l e n + 1 ,而是誰觸發的len+1len+1 l e n + 1
模板
struct node
{ int len
; int fa
; int son
[ maxc
] ;
} t
[ maxn
] ;
模擬從主鏈的前一個開始跳后綴鏈接,并對于鏈接上的沒有該字符邊的每一個點都連出一條新字符邊
while ( pre
&& ! t
[ pre
] . son
[ c
] ) t
[ pre
] . son
[ c
] = now
, pre
= t
[ pre
] . fa
;
跳到根,代表這是首個出現的字符,他只能鏈接最初的根節點了
if ( ! pre
) t
[ now
] . fa
= 1 ;
否則,如果路上找到了,滿足lenlen l e n 的關系,直接后綴鏈接指過去即可
int u
= t
[ pre
] . son
[ c
] ;
if ( t
[ u
] . len
== t
[ pre
] . len
+ 1 ) t
[ now
] . fa
= u
;
復制該點,并進行有關該點的所有信息重改 ①原點連出的點,新點也要連出 ②連入原點的點,變成連入新點 ③原點和新點間也需建立聯系,新點是原點的后綴鏈接
else { int v
= ++ tot
; t
[ v
] = t
[ u
] ; t
[ v
] . len
= t
[ pre
] . len
+ 1 ; t
[ u
] . fa
= t
[ now
] . fa
= v
; while ( pre
&& t
[ pre
] . son
[ c
] == u
) t
[ pre
] . son
[ c
] = v
, pre
= t
[ pre
] . fa
;
}
習題
洛谷后綴自動機模板題
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std
;
#define maxn 2000005
vector
< int > G
[ maxn
] ;
struct node
{ int fa
, len
; int son
[ 30 ] ;
} t
[ maxn
] ;
char s
[ maxn
] ;
int last
= 1 , tot
= 1 ;
long long ans
;
int siz
[ maxn
] ; void insert ( int c
) { int pre
= last
, now
= last
= ++ tot
; siz
[ tot
] = 1 ; t
[ now
] . len
= t
[ pre
] . len
+ 1 ; while ( pre
&& ! t
[ pre
] . son
[ c
] ) t
[ pre
] . son
[ c
] = now
, pre
= t
[ pre
] . fa
; if ( ! pre
) t
[ now
] . fa
= 1 ; else { int u
= t
[ pre
] . son
[ c
] ; if ( t
[ u
] . len
== t
[ pre
] . len
+ 1 ) t
[ now
] . fa
= u
; else { int v
= ++ tot
; t
[ v
] = t
[ u
] ; t
[ v
] . len
= t
[ pre
] . len
+ 1 ; t
[ u
] . fa
= t
[ now
] . fa
= v
; while ( pre
&& t
[ pre
] . son
[ c
] == u
) t
[ pre
] . son
[ c
] = v
, pre
= t
[ pre
] . fa
; } }
} void dfs ( int u
) { for ( int i
= 0 ; i
< G
[ u
] . size ( ) ; i
++ ) { int v
= G
[ u
] [ i
] ; dfs ( v
) ; siz
[ u
] + = siz
[ v
] ; } if ( siz
[ u
] != 1 ) ans
= max ( ans
, 1ll * siz
[ u
] * t
[ u
] . len
) ;
} int main ( ) { scanf ( "%s" , s
) ; int len
= strlen ( s
) ; for ( int i
= 0 ; i
< len
; i
++ ) insert ( s
[ i
] - 'a' ) ; for ( int i
= 2 ; i
<= tot
; i
++ ) G
[ t
[ i
] . fa
] . push_back ( i
) ; dfs ( 1 ) ; printf ( "%lld" , ans
) ; return 0 ;
}
品酒大會
solution 有一個SAMSAM S A M 常用結論:前綴i,ji,j i , j 的最長公共后綴 =parenttree=parent\ tree = p a r e n t ? t r e e 上前綴i,ji,j i , j 分別指向的點u,vu,v u , v 的lcalca l c a 反映在后綴自動機上的節點代表的最長子串 將本題的字符串倒過來建后綴自動機,在自動機上進行樹上dpdp d p ,最后從后往前進行更新即可code
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std
;
#define inf 0x7f7f7f7f
#define int long long
#define maxn 600005
struct node
{ int len
, fa
; int son
[ 30 ] ;
} t
[ maxn
] ;
vector
< int > G
[ maxn
] ;
int last
= 1 , cnt
= 1 , n
;
char s
[ maxn
] ;
int a
[ maxn
] , f
[ maxn
] , tot
[ maxn
] , siz
[ maxn
] , maxx
[ maxn
] , minn
[ maxn
] ; void insert ( int x
, int w
) { int pre
= last
, now
= last
= ++ cnt
; siz
[ now
] = 1 , t
[ now
] . len
= t
[ pre
] . len
+ 1 ; maxx
[ now
] = minn
[ now
] = w
; while ( pre
&& ! t
[ pre
] . son
[ x
] ) t
[ pre
] . son
[ x
] = now
, pre
= t
[ pre
] . fa
; if ( ! pre
) t
[ now
] . fa
= 1 ; else { int u
= t
[ pre
] . son
[ x
] ; if ( t
[ u
] . len
== t
[ pre
] . len
+ 1 ) t
[ now
] . fa
= u
; else { int v
= ++ cnt
; maxx
[ v
] = - inf
, minn
[ v
] = inf
; t
[ v
] = t
[ u
] ; t
[ v
] . len
= t
[ pre
] . len
+ 1 ; t
[ u
] . fa
= t
[ now
] . fa
= v
; while ( pre
&& t
[ pre
] . son
[ x
] == u
) t
[ pre
] . son
[ x
] = v
, pre
= t
[ pre
] . fa
; } }
} bool check ( int u
) { return maxx
[ u
] != - inf
&& minn
[ u
] != inf
;
} void dfs ( int u
) { for ( int i
= 0 ; i
< G
[ u
] . size ( ) ; i
++ ) { int v
= G
[ u
] [ i
] ; dfs ( v
) ; tot
[ t
[ u
] . len
] + = siz
[ u
] * siz
[ v
] ; siz
[ u
] + = siz
[ v
] ; if ( check ( u
) ) f
[ t
[ u
] . len
] = max ( f
[ t
[ u
] . len
] , max ( maxx
[ u
] * maxx
[ v
] , minn
[ u
] * minn
[ v
] ) ) ; maxx
[ u
] = max ( maxx
[ u
] , maxx
[ v
] ) ; minn
[ u
] = min ( minn
[ u
] , minn
[ v
] ) ; }
} signed main ( ) { memset ( f
, - 0x7f , sizeof ( f
) ) ; scanf ( "%d %s" , & n
, s
+ 1 ) ; for ( int i
= 1 ; i
<= n
; i
++ ) scanf ( "%lld" , & a
[ i
] ) ; for ( int i
= n
; i
; i
-- ) insert ( s
[ i
] - 'a' , a
[ i
] ) ; for ( int i
= 2 ; i
<= cnt
; i
++ ) G
[ t
[ i
] . fa
] . push_back ( i
) ; dfs ( 1 ) ; for ( int i
= n
- 1 ; ~ i
; i
-- ) tot
[ i
] + = tot
[ i
+ 1 ] , f
[ i
] = max ( f
[ i
] , f
[ i
+ 1 ] ) ; for ( int i
= 0 ; i
< n
; i
++ ) printf ( "%lld %lld\n" , tot
[ i
] , ( tot
[ i
] ? f
[ i
] : 0 ) ) ; return 0 ;
}
[HEOI2015]最短不公共子串
solution 做此題需要了解序列自動機 然后就是很無腦的四個bfsbfs b f s 跑 子串就是跑后綴自動機 子序列就是跑序列自動機code
#include <queue>
#include <cstdio>
#include <cstring>
using namespace std
;
#define maxn 5000
char a
[ maxn
] , b
[ maxn
] ; struct SAM
{ struct node
{ int len
, fa
; int son
[ 30 ] ; } t
[ maxn
] ; int last
, cnt
; SAM ( ) { last
= cnt
= 1 ; } void insert ( int x
) { int pre
= last
, now
= last
= ++ cnt
; t
[ now
] . len
= t
[ pre
] . len
+ 1 ; while ( pre
&& ! t
[ pre
] . son
[ x
] ) t
[ pre
] . son
[ x
] = now
, pre
= t
[ pre
] . fa
; if ( ! pre
) t
[ now
] . fa
= 1 ; else { int u
= t
[ pre
] . son
[ x
] ; if ( t
[ u
] . len
== t
[ pre
] . len
+ 1 ) t
[ now
] . fa
= u
; else { int v
= ++ cnt
; t
[ v
] = t
[ u
] ; t
[ v
] . len
= t
[ pre
] . len
+ 1 ; t
[ u
] . fa
= t
[ now
] . fa
= v
; while ( pre
&& t
[ pre
] . son
[ x
] == u
) t
[ pre
] . son
[ x
] = v
, pre
= t
[ pre
] . fa
; } } } } SamA
, SamB
; struct SEQ
{ int nxt
[ maxn
] [ 30 ] , last
[ 30 ] ; SEQ ( ) { memset ( nxt
, 0 , sizeof ( nxt
) ) ; memset ( last
, 0 , sizeof ( last
) ) ; } void build ( int n
, char * s
) { for ( int i
= n
; ~ i
; i
-- ) { for ( int j
= 0 ; j
< 26 ; j
++ ) if ( last
[ j
] ) nxt
[ i
+ 1 ] [ j
] = last
[ j
] ; if ( i
) last
[ s
[ i
] - 'a' ] = i
+ 1 ; } } } SeqA
, SeqB
; struct node
{ int x
, y
, dep
; node ( ) { } node ( int X
, int Y
, int Dep
) { x
= X
, y
= Y
, dep
= Dep
; }
} ;
queue
< node
> q
;
bool vis
[ maxn
] [ maxn
] ; void init ( ) { memset ( vis
, 0 , sizeof ( vis
) ) ; while ( ! q
. empty ( ) ) q
. pop ( ) ; vis
[ 1 ] [ 1 ] = 1 , q
. push ( node ( 1 , 1 , 0 ) ) ;
} int bfs1 ( ) { init ( ) ; while ( ! q
. empty ( ) ) { node now
= q
. front ( ) ; q
. pop ( ) ; for ( int i
= 0 ; i
< 26 ; i
++ ) { int sonA
= SamA
. t
[ now
. x
] . son
[ i
] ; int sonB
= SamB
. t
[ now
. y
] . son
[ i
] ; if ( vis
[ sonA
] [ sonB
] ) continue ; else if ( sonA
&& ! sonB
) return now
. dep
+ 1 ; else if ( sonA
&& sonB
) { vis
[ sonA
] [ sonB
] = 1 ; q
. push ( node ( sonA
, sonB
, now
. dep
+ 1 ) ) ; } } } return - 1 ;
} int bfs2 ( ) { init ( ) ; while ( ! q
. empty ( ) ) { node now
= q
. front ( ) ; q
. pop ( ) ; for ( int i
= 0 ; i
< 26 ; i
++ ) { int sonA
= SamA
. t
[ now
. x
] . son
[ i
] ; int sonB
= SeqB
. nxt
[ now
. y
] [ i
] ; if ( vis
[ sonA
] [ sonB
] ) continue ; else if ( sonA
&& ! sonB
) return now
. dep
+ 1 ; else if ( sonA
&& sonB
) { vis
[ sonA
] [ sonB
] = 1 ; q
. push ( node ( sonA
, sonB
, now
. dep
+ 1 ) ) ; } } } return - 1 ;
} int bfs3 ( ) { init ( ) ; while ( ! q
. empty ( ) ) { node now
= q
. front ( ) ; q
. pop ( ) ; for ( int i
= 0 ; i
< 26 ; i
++ ) { int sonA
= SeqA
. nxt
[ now
. x
] [ i
] ; int sonB
= SamB
. t
[ now
. y
] . son
[ i
] ; if ( vis
[ sonA
] [ sonB
] ) continue ; else if ( sonA
&& ! sonB
) return now
. dep
+ 1 ; else if ( sonA
&& sonB
) { vis
[ sonA
] [ sonB
] = 1 ; q
. push ( node ( sonA
, sonB
, now
. dep
+ 1 ) ) ; } } } return - 1 ;
} int bfs4 ( ) { init ( ) ; while ( ! q
. empty ( ) ) { node now
= q
. front ( ) ; q
. pop ( ) ; for ( int i
= 0 ; i
< 26 ; i
++ ) { int sonA
= SeqA
. nxt
[ now
. x
] [ i
] ; int sonB
= SeqB
. nxt
[ now
. y
] [ i
] ; if ( vis
[ sonA
] [ sonB
] ) continue ; else if ( sonA
&& ! sonB
) return now
. dep
+ 1 ; else if ( sonA
&& sonB
) { vis
[ sonA
] [ sonB
] = 1 ; q
. push ( node ( sonA
, sonB
, now
. dep
+ 1 ) ) ; } } } return - 1 ;
} int main ( ) { scanf ( "%s %s" , a
+ 1 , b
+ 1 ) ; int lena
= strlen ( a
+ 1 ) , lenb
= strlen ( b
+ 1 ) ; for ( int i
= 1 ; i
<= lena
; i
++ ) SamA
. insert ( a
[ i
] - 'a' ) ; for ( int i
= 1 ; i
<= lenb
; i
++ ) SamB
. insert ( b
[ i
] - 'a' ) ; SeqA
. build ( lena
, a
) ; SeqB
. build ( lenb
, b
) ; printf ( "%d\n%d\n%d\n%d\n" , bfs1 ( ) , bfs2 ( ) , bfs3 ( ) , bfs4 ( ) ) ; return 0 ;
}
字符串
這題運用的思想主要是廣義后綴自動機 ,即將多個字符串建在一個后綴自動機上 其實并沒有什么新穎之處,只需在擴展的時候帶一個這個字符屬于哪個字符串的編號即可
假設已經建好了自動機,接下來考慮兩個長度為kk k 的子串之間如何一一對應修改 這個時候如果將其放到parenttreeparent\ tree p a r e n t ? t r e e 上考慮的話,就簡單了
其實可以猜想一下,剛開始我就想到了虛樹 的性質,即相鄰兩兩配對 不難證明,的確應該相鄰兩個不同屬類的子串配對
前綴i,ji,j i , j 的最長公共后綴 =parenttree=parent\ tree = p a r e n t ? t r e e 上前綴i,ji,j i , j 分別指向的點u,vu,v u , v 的lcalca l c a 反映在后綴自動機上的節點代表的最長子串
也就是最后變成深搜一棵樹的模樣,記得特判可能lcalca l c a 代表的最長子串長度≥k\ge k ≥ k 此時是不需要代價的
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std
;
#define maxn 600005
struct node
{ int len
, fa
; int son
[ 30 ] ;
} t
[ maxn
] ;
vector
< int > G
[ maxn
] ;
int n
, k
, last
= 1 , cnt
= 1 ;
long long ans
;
char a
[ maxn
] , b
[ maxn
] ;
int type
[ maxn
] ;
int tot
[ maxn
] [ 3 ] ; void insert ( int x
, int s
, int pos
) { int pre
= last
, now
= last
= ++ cnt
; t
[ now
] . len
= t
[ pre
] . len
+ 1 ; while ( pre
&& ! t
[ pre
] . son
[ x
] ) t
[ pre
] . son
[ x
] = now
, pre
= t
[ pre
] . fa
; if ( ! pre
) t
[ now
] . fa
= 1 ; else { int u
= t
[ pre
] . son
[ x
] ; if ( t
[ u
] . len
== t
[ pre
] . len
+ 1 ) t
[ now
] . fa
= u
; else { int v
= ++ cnt
; t
[ v
] = t
[ u
] ; t
[ v
] . len
= t
[ pre
] . len
+ 1 ; t
[ u
] . fa
= t
[ now
] . fa
= v
; while ( pre
&& t
[ pre
] . son
[ x
] == u
) t
[ pre
] . son
[ x
] = v
, pre
= t
[ pre
] . fa
; } } if ( pos
>= k
) type
[ now
] = s
;
} void dfs ( int u
) { tot
[ u
] [ type
[ u
] ] ++ ; for ( int i
= 0 ; i
< G
[ u
] . size ( ) ; i
++ ) { int v
= G
[ u
] [ i
] ; dfs ( v
) ; tot
[ u
] [ 1 ] + = tot
[ v
] [ 1 ] ; tot
[ u
] [ 2 ] + = tot
[ v
] [ 2 ] ; } if ( tot
[ u
] [ 1 ] >= tot
[ u
] [ 2 ] ) { int x
= max ( 0 , k
- t
[ u
] . len
) ; ans
+ = 1ll * x
* tot
[ u
] [ 2 ] ; tot
[ u
] [ 1 ] - = tot
[ u
] [ 2 ] ; tot
[ u
] [ 2 ] = 0 ; } else { int x
= max ( 0 , k
- t
[ u
] . len
) ; ans
+ = 1ll * x
* tot
[ u
] [ 1 ] ; tot
[ u
] [ 2 ] - = tot
[ u
] [ 1 ] ; tot
[ u
] [ 1 ] = 0 ; }
} int main ( ) { scanf ( "%d %d %s %s" , & n
, & k
, a
+ 1 , b
+ 1 ) ; reverse ( a
+ 1 , a
+ n
+ 1 ) ; reverse ( b
+ 1 , b
+ n
+ 1 ) ; for ( int i
= 1 ; i
<= n
; i
++ ) insert ( a
[ i
] - 'a' , 1 , i
) ; for ( int i
= 1 ; i
<= n
; i
++ ) insert ( b
[ i
] - 'a' , 2 , i
) ; for ( int i
= 2 ; i
<= cnt
; i
++ ) G
[ t
[ i
] . fa
] . push_back ( i
) ; dfs ( 1 ) ; printf ( "%lld" , ans
) ; return 0 ;
}
總結
以上是生活随笔 為你收集整理的后缀自动机(SAM)构造实现过程演示+习题集锦 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。