前言
第(3)次尝试学习后缀自动机……下定决心不再背板子
参考资料:
2012年noi冬令营clj讲稿
前置知识:Trie树
简介
后缀自动机,顾名思义是能识别所有后缀的自动机。那么就要从两个方面入手:什么是自动机,以及怎样让自动机识别所有后缀。
其实识别所有后缀用Trie树就可以做到,把所有后缀插到Trie里即可。但是当点数比较多的时候,Trie树(O(n^2))的时间和空间复杂度就吃不消了。于是就需要后缀自动机。
自动机
有限状态自动机能识别字符串。自动机由(5)个部分组成,分别是字符集(Alpha),状态集合(State),初始状态(Init),结束状态集合(End),和状态转移函数(Trans)。如果一个自动机(A)能识别字符串(S),那么记(A(S)=1(true)),否则(A(S)=0(false))。
定义(Trans(Rin {Statecup {NULL}},ch/str))为状态(R)后加字符(ch)或字符串(str)所达到的状态。如果不存在则记为(NULL)。
容易发现自动机(A)能识别的串就是所有的(S)使得(Trans(Init,S)in End)。记(Reg(A)={S:Trans(Init,S)in End})。
后缀自动机概念
由上面自动机的概念,我们可以知道,一个(String)的SAM(suffix automaton),(Sin Reg(SAM))当且仅当(S)是(String)的后缀。然而似乎Trie仍然可以满足……我们需要压缩Trie的状态!
下面不妨令(String="aabab")。(n)为(Length(String))。
我们定义(String)的子串(S)出现位置的右端点集合为(EndPos(S))。例如例子中(EndPos("ab")={3,5})。那么,关于(EndPos)我们有如下几个结论:
- 如果两个串的(EndPos)相同,那么其中一个一定是另一个的后缀。
- 对于任意两个串(S_1,S_2),(Length(S_1)leqslant Length(S_2)),那么(EndPos(S_2)subseteq EndPos(S_1))或(EndPos(S_1)cap EndPos(S_2)=emptyset)。
- 对于所有(EndPos(S))相等的(S),记这些(S)中长度最大的长度为(MaxLen),长度最小的为(MinLen),那么对于(forall i(MinLen leqslant ileqslant MaxLen), exists S'(EndPos(S')=EndPos(S), Length(S')=i))。
- 不同的(EndPos)最多有(O(n))类。
前(3)点还是容易想象的,下面是对于第(4)点的证明:
不难发现(forall S, EndPos(S)subseteq EndPos(""))。对于某个特定的(EndPos)集合(X),取最长的(S)使得(EndPos(S)=X)。在(X)前面加两个不同的字符(x,y),那么(EndPos(xS)cap EndPos(yS)=emptyset)。而实际上还有
[left|igcup_{xin Alpha}EndPos(xS)=EndPos(S) ight| ]所以我们可以将这看成一个划分,划分关系构成一棵树,叫做Parent树。其中,一个节点的父亲的(MaxLen)是这个节点的(MinLen-1)。这棵树最多有(2n-1)个节点,即不同的(EndPos)的个数是(O(n))的。
可以看图理解一下(图中黑色部分,黄色为满足某个特定的(EndPos)的最长(S)):
如果将这个数构建成一个完整的自动机,我们还需要定义:
- (Init):根节点;
- (End):图中带橙色叉的节点;
- (Trans):图中红色的边;
同时我们还可以说SAM的边数是(O(n))的。坑,待填
如何构建后缀自动机
先放两张图:
如果我们已经构建好了("aaba")的SAM:
我们要构建("aabab")的SAM:
上面这张图有点错误,从({2})指向({3,5})的边应该指向({3}),从({3})应该有指向({4})的值为(a)的边。这点会在下面的构造过程中讲到。
我们发现,只需要更改(EndPos)中包含最后一个位置的点就可以了。如果我们记录了(Last),那需要处理的节点就是(Last)的祖先。不妨从叶子向根记做(v_1,v_2,dots)。由于从叶子向根,(|EndPos|)是不断增大的。所以我们可以找到第一个(Trans(v_i,x))不为空的点。将(Trans(v_t,x))为空的指向新点(newPoint)即可,而对于另一些我们需要分类讨论。不妨记(q=Trans(v_t,x))。如果强行加入(x),那么会使(q)的(MaxLen)变小(当然,如果不会,那么构造就结束了)。加入(x)后,实际上(q)被分为了两个部分:
所以建一个新的点(newq)解决这个问题。(newq)继承了(q)除了(MaxLen)之外的所有信息。
同时,我们还要将原图中原来指向(q)的点指向(nq)。这样就结束了!
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 1000010;
struct suffixAutomaton {
int Link[ Maxn << 1 ], Len[ Maxn << 1 ], Child[ Maxn << 1 ][ 26 ];
int Last, Used;
inline void Init() {
memset( Link, 0, sizeof( Link ) );
memset( Len, 0, sizeof( Len ) );
memset( Child, 0, sizeof( Child ) );
Last = 1; Used = 1;
return;
}
void Build( int Ch ) {
int Now = ++Used, p;
Len[ Now ] = Len[ Last ] + 1;
for( p = Last; p && Child[ p ][ Ch ] == 0; p = Link[ p ] ) Child[ p ][ Ch ] = Now;
Last = Now;
if( !p ) { Link[ Now ] = 1; return; }
int q = Child[ p ][ Ch ];
if( Len[ p ] + 1 == Len[ q ] ) { Link[ Now ] = q; return; }
int Clone = ++Used;
Len[ Clone ] = Len[ p ] + 1; Link[ Clone ] = Link[ q ];
for( int i = 0; i < 26; ++i ) Child[ Clone ][ i ] = Child[ q ][ i ];
for( ; p && Child[ p ][ Ch ] == q; p = Link[ p ] ) Child[ p ][ Ch ] = Clone;
Link[ q ] = Link[ Now ] = Clone;
return;
}
};
suffixAutomaton SuffixAutomaton;
char Ch[ Maxn ];
int Len;
int main() {
scanf( "%s", Ch );
Len = strlen( Ch );
SuffixAutomaton.Init();
for( int i = 0; i < Len; ++i )
SuffixAutomaton.Build( Ch[ i ] - 'a' );
return 0;
}
练习题
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 1000010;
int Ref[ Maxn ], Arr[ Maxn << 1 ];
struct suffixAutomaton {
int Link[ Maxn << 1 ], Len[ Maxn << 1 ], Child[ Maxn << 1 ][ 26 ];
int Last, Used;
//extra
int Size[ Maxn << 1 ];
inline void Init() {
memset( Link, 0, sizeof( Link ) );
memset( Len, 0, sizeof( Len ) );
memset( Child, 0, sizeof( Child ) );
Last = 1; Used = 1;
return;
}
void Build( int Ch ) {
int Now = ++Used, p;
Len[ Now ] = Len[ Last ] + 1;
Size[ Now ] = 1;//不是copy的点才能做算大小
for( p = Last; p && Child[ p ][ Ch ] == 0; p = Link[ p ] ) Child[ p ][ Ch ] = Now;
Last = Now;
if( !p ) { Link[ Now ] = 1; return; }
int q = Child[ p ][ Ch ];
if( Len[ p ] + 1 == Len[ q ] ) { Link[ Now ] = q; return; }
int Clone = ++Used;
Len[ Clone ] = Len[ p ] + 1; Link[ Clone ] = Link[ q ];
for( int i = 0; i < 26; ++i ) Child[ Clone ][ i ] = Child[ q ][ i ];
for( ; p && Child[ p ][ Ch ] == q; p = Link[ p ] ) Child[ p ][ Ch ] = Clone;
Link[ q ] = Link[ Now ] = Clone;
return;
}
inline void CollectSize( int MaxLen ) {//按照DAG的逆序求大小
memset( Ref, 0, sizeof( Ref ) );
memset( Arr, 0, sizeof( Arr ) );
for( int i = 1; i <= Used; ++i ) ++Ref[ Len[ i ] ];
for( int i = 1; i <= MaxLen; ++i ) Ref[ i ] += Ref[ i - 1 ];
for( int i = 1; i <= Used; ++i ) Arr[ Ref[ Len[ i ] ]-- ] = i;
for( int i = Used; i >= 1; --i ) Size[ Link[ Arr[ i ] ] ] += Size[ Arr[ i ] ];
return;
}
};
suffixAutomaton SuffixAutomaton;
char Ch[ Maxn ];
int Len;
int main() {
scanf( "%s", Ch );
Len = strlen( Ch );
SuffixAutomaton.Init();
for( int i = 0; i < Len; ++i ) SuffixAutomaton.Build( Ch[ i ] - 'a' );
SuffixAutomaton.CollectSize( Len );
int Ans = 0;
for( int i = 1; i <= SuffixAutomaton.Used; ++i )
if( SuffixAutomaton.Size[ i ] > 1 )
Ans = max( Ans, SuffixAutomaton.Len[ i ] * SuffixAutomaton.Size[ i ] );
printf( "%d
", Ans );
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 100010;
int Ref[ Maxn ], Arr[ Maxn << 1 ];
struct suffixAutomaton {
int Link[ Maxn << 1 ], Len[ Maxn << 1 ], Child[ Maxn << 1 ][ 26 ];
int Last, Used;
//extra
int Size[ Maxn << 1 ];
inline void Init() {
memset( Link, 0, sizeof( Link ) );
memset( Len, 0, sizeof( Len ) );
memset( Child, 0, sizeof( Child ) );
memset( Size, 0, sizeof( Size ) );
Last = 1; Used = 1;
return;
}
void Build( int Ch ) {
int Now = ++Used, p;
Len[ Now ] = Len[ Last ] + 1;
Size[ Now ] = 1;//不是copy的点才能做算大小
for( p = Last; p && Child[ p ][ Ch ] == 0; p = Link[ p ] ) Child[ p ][ Ch ] = Now;
Last = Now;
if( !p ) { Link[ Now ] = 1; return; }
int q = Child[ p ][ Ch ];
if( Len[ p ] + 1 == Len[ q ] ) { Link[ Now ] = q; return; }
int Clone = ++Used;
Len[ Clone ] = Len[ p ] + 1; Link[ Clone ] = Link[ q ];
for( int i = 0; i < 26; ++i ) Child[ Clone ][ i ] = Child[ q ][ i ];
for( ; p && Child[ p ][ Ch ] == q; p = Link[ p ] ) Child[ p ][ Ch ] = Clone;
Link[ q ] = Link[ Now ] = Clone;
return;
}
inline void CollectSize( int MaxLen ) {//按照DAG的逆序求大小
memset( Ref, 0, sizeof( Ref ) );
memset( Arr, 0, sizeof( Arr ) );
for( int i = 1; i <= Used; ++i ) ++Ref[ Len[ i ] ];
for( int i = 1; i <= MaxLen; ++i ) Ref[ i ] += Ref[ i - 1 ];
for( int i = 1; i <= Used; ++i ) Arr[ Ref[ Len[ i ] ]-- ] = i;
for( int i = Used; i >= 1; --i ) Size[ Link[ Arr[ i ] ] ] += Size[ Arr[ i ] ];
return;
}
};
suffixAutomaton SuffixAutomaton;
char Ch[ Maxn ];
int Len;
int Count[ Maxn ];
void Work() {
scanf( "%s", Ch );
Len = strlen( Ch );
SuffixAutomaton.Init();
for( int i = 0; i < Len; ++i ) SuffixAutomaton.Build( Ch[ i ] - 'a' );
SuffixAutomaton.CollectSize( Len );
int k; scanf( "%d", &k );
int Max = 1, Ans = -1;
memset( Count, 0, sizeof( Count ) );
for( int i = 1; i <= SuffixAutomaton.Used; ++i )
if( SuffixAutomaton.Size[ i ] == k ) {
--Count[ SuffixAutomaton.Len[ i ] + 1 ];
++Count[ SuffixAutomaton.Len[ SuffixAutomaton.Link[ i ] ] + 1 ];
}
for( int i = 1; i <= Len; ++i ) Count[ i ] += Count[ i - 1 ];
for( int i = 0; i <= Len; ++i )
if( Count[ i ] >= Max ) {
Max = Count[ i ];
Ans = i;
}
printf( "%d
", Ans );
return;
}
int main() {
int TestCases; scanf( "%d", &TestCases );
for( ; TestCases--; ) Work();
return 0;
}