后缀自动机:
首先在理解kmp , ac自动机 , 后缀数组的基础上 , 我们再讨论后缀自动机
http://hihocoder.com/problemset/problem/1441
真的是太经典了,好好看看绝对理解。
下图字符串 S="aabbabd"
性质:
- 字符串的后缀都能到 终结态
- S的所有子串都能到达一个合法态(从某个节点转移到另一个节点)
- 不是S子串的 字符串 最终会没有路可走
子串结束集合——endpos字符集:
把所有子串的endpos 都求出来,两个子串的endpos相等 归为一类
每一个endpos节点都记录了 一个的原串后缀的前缀,它可以包含很多个子串,最长的用len[s] 表示,最短的是shortlen[s]表示。
我们知道一个 子串 的所有后缀不是在同一个endpos的,它还可能在别的endpos里,我们的link就让它其他的endpos后缀指向它本身。也就只说从前向后越来越长,但是在同一个后缀上。
匹配的时候如果找一个匹配串的T[i] 找不到,那么就需要在保持之前已经匹配的基础上,向主串的后面跳,看看是否能找到其他的子串 S", 且S" 的后缀和刚才我们匹配成功的后缀一样,如果找到了,就继续对比T[i]是不是能成功匹配。
link的作用:
新建一个cur状态节点
- 当前插入一个字母c,变到cur状态,它通过link向前跳,直到空节点都没一个出现一个pre状态,pre状态的下一个字符是c,那么我们让link[cur] = 0
- 如果有一个q状态,它是由前一个p状态通过c转移过来的就需要分两种情况
- q状态 和 前一个p状态的关系是 len[q] = len[p] + 1 , 我们就可以让link[cur] = q
- 否则就出现了len[q] > len[p] + 1,我们需要copy一个 q 的clone,让len[clone] = len[p] + 1,让cur指向clone,然后 开始走 p 的link 看是否有指向q的转移,如果有就让它指向clone
- 最终停下后,我们更新last=cur
时空复杂度:
边和点 根据CLJ的 PPT上所说 , 都是O(n)的 , 时间复杂度 也是 O(n) 的 ,毕竟有fail 指针加速
我们这里所说的fail指针和link指针是同一个东西,只不过我们后缀自动机中的性质保证了我们的fail可以从后向前指的都是同一个endpos的后缀。
而AC自动机没有相同后缀这一性质,它是把匹配串建出字典树,不能保证相同后缀的len逐渐递减,只能在匹配失败后向前跳,保证它能匹配到不同的 待匹配串。
那么就先来个 最简单的 后缀自动机 跑两个字符串的 最长连续 公共子串 的大小
当个简单模板吧
题目:SPOJ - LCS
给两个字符串 求 最长公共子串
const int Maxn = 250007;
int N , M;
char s[Maxn];
struct SAM{
struct node {
int len,fail;
//map<char,int> next;
int next[26];
void init() { len = 0 , fail = 0; }
};
node st[Maxn*4];
int sz, last;
void sa_init() {
sz = last = 0;
st[last].fail = -1;
}
void sa_extend (char c) {
c = c - 'a';
/*
//建两次树,重复的就要跳过
if(st[last].next[c] && st[last].len + 1 == st[st[last].next[c]].len){
last = st[last].next[c];
return;
}
*/
int p = last;
int np = last = ++sz;
st[np].len = st[p].len + 1;
for (; p != -1 && !st[p].next[c]; p=st[p].fail)
st[p].next[c] = np;
if (p == -1){
st[np].fail = 0;
} else {
int q = st[p].next[c];
if (st[p].len + 1 == st[q].len){
st[np].fail = q;
} else {
int nq = ++sz;
//st[nq].next = st[q].next;
for(int i = 0 ; i < 26 ; i++) st[nq].next[i] = st[q].next[i];
st[nq].len = st[p].len + 1;
st[nq].fail = st[q].fail;
st[q].fail = st[np].fail = nq;
for (; p != -1 && st[p].next[c]==q; p=st[p].fail)
st[p].next[c] = nq;
}
}
}
int suffix_Automaton(){
int p = 0 , len = 0;
int ans = 0;
for(int i = 0 ; s[i] ; i++){
char c = s[i] - 'a';
while(p != -1 && st[p].next[c] == 0){
p = st[p].fail;
}
if(p == -1){
p = len = 0;
} else {
len = min(len , st[p].len);
p = st[p].next[c];
len++;
}
ans = max(ans , len);
}
return ans;
}
}sam;
int main()
{
scanf(" %s",s);
int len = strlen(s);
sam.sa_init();
for(int i = 0 ; i < len ; i++){
sam.sa_extend(s[i]);
}
scanf(" %s",s);
int ans = 0;
ans = sam.suffix_Automaton();
printf("%d\n",ans);
return 0;
}
这道题由于是 250000 的字符串 , 所以我用了map超时 了 , 虽然感觉每个节点只有26个 , map用起来和 int数组一样快 , 但是冒有办法。
题目:SPOJ - LCS2
给 t (1 < t<= 10)个字符串 , 求最长公共子串
topo部分:
topo是用来求一个字符串匹配成功以后,我们把整个图拓补出来,我们在上面的endpos字符集里面发现匹配的 len长度 可能会偏长,所以 拓补 之后如果找到了匹配成功的,那么将之前的 爷爷fail指针 的len向后 传递 给 父亲fail节点
这样从(sz ~~ 1) 的过程 中就能维护处 每次匹配 成功维护出来的 最长公共子串的 最小值。
这里 就不能 像之前那样 直接建树 然后跑一遍s a匹配就求出答案的。
之前做了一个 Codeforces 427D (给出两个字符串,求最小公共子串长度,使这个子串仅在两个串均出现一次), 被里面的两次建树思想带跑了,写成了多次建树,然后更新里面的一个新变量sum , 但是不能保证 单独一个SS1串 里面 不多次出现的子串str 。如果第一个串里出现了 >= 10次 , 后面2 ~ 8个串没出现,最后一个串出现了一次str ,那么匹配的时候就会算错。
const int Inf = 1e9 + 7;
const int Maxn = 100007;
int N , M;
int fc[Maxn<<2] , fmn[Maxn<<2];
char s[Maxn];
struct SAM{
int a[Maxn<<2] , b[Maxn<<2];
struct node {
int len,fail;
int sum;
//map<char,int> next;
int next[26];
void init() { len = 0 , fail = 0; }
};
node st[Maxn << 1];
int sz, last;
void sa_init() {
sz = last = 0;
st[last].fail = -1;
fill(fc , fc+Maxn*2 , Inf);
}
void sa_extend (char c) {
c = c - 'a';
/*
//建多次树,重复的就要跳过
if(st[last].next[c] && st[last].len + 1 == st[st[last].next[c]].len){
last = st[last].next[c];
//
st[last].sum++;
return;
}
*/
int p = last;
int np = last = ++sz;
st[np].len = st[p].len + 1;
for (; p != -1 && !st[p].next[c]; p=st[p].fail)
st[p].next[c] = np;
if (p == -1){
st[np].fail = 0;
} else {
int q = st[p].next[c];
if (st[p].len + 1 == st[q].len){
st[np].fail = q;
} else {
int nq = ++sz;
//st[nq].next = st[q].next;
for(int i = 0 ; i < 26 ; i++) st[nq].next[i] = st[q].next[i];
st[nq].len = st[p].len + 1;
st[nq].fail = st[q].fail;
st[q].fail = st[np].fail = nq;
for (; p != -1 && st[p].next[c]==q; p=st[p].fail)
st[p].next[c] = nq;
}
}
}
void Topo(){
int os = 1;
for(int i = os ; i <= sz ; i++){
++a[st[i].len];
}
for(int i = os ; i <= sz ; i++){
a[i] += a[i-1];
}
for(int i = os ; i <= sz ; i++){
b[a[st[i].len]--] = i;
}
}
void suffix_Automaton(){
Clear(fmn , 0);
int p = 0 , len = 0;
int ans = 0;
for(int i = 0 ; s[i] ; i++){
char c = s[i] - 'a';
while(p != -1 && st[p].next[c] == 0){
p = st[p].fail;
}
if(p == -1){
p = len = 0;
} else {
len = min(len , st[p].len);
p = st[p].next[c];
len++;
}
fmn[p] = max(fmn[p] , len);
}
for(int i = sz ; i >= 1 ; i--){
int pos = b[i];
fc[pos] = min(fc[pos] , fmn[pos]);
if(fmn[pos]){
//之前的会出现重复的子串,所以要将之前的len向后移
//它的len并没有被改变
fmn[st[pos].fail] = st[st[pos].fail].len;
}
}
}
}sam;
int main()
{
sam.sa_init();
scanf(" %s",s);
for(int i = 0 ; s[i] ; i++){
sam.sa_extend(s[i]);
}
sam.Topo();
while(~scanf(" %s",s)){
sam.suffix_Automaton();
}
int ans = 0;
for(int i = 1 ; i <= sam.sz ; i++){
ans = max(ans , fc[i]);
}
printf("%d\n",max(0,ans));
return 0;
}
感谢大佬列出的例题:(https://www.cnblogs.com/xzyxzy/p/9186759.html)
- [hihocoder1441]后缀自动机一·基本概念 http://hihocoder.com/problemset/problem/1441
- [hihocoder1445]后缀自动机二·重复旋律5 http://hihocoder.com/problemset/problem/1445
- [hihocoder1449]后缀自动机三·重复旋律6 http://hihocoder.com/problemset/problem/1449
- [hihocoder1457]后缀自动机四·重复旋律7 http://hihocoder.com/problemset/problem/1457
- [hihocoder1465]后缀自动机五·重复旋律8 http://hihocoder.com/problemset/problem/1465
- [HihoCoder1413]Rikka with String
- [Luogu3804]【模板】后缀自动机 https://www.luogu.org/problemnew/show/P3804
- [BZOJ4516][SDOI2016]生成魔咒
- [BZOJ3998][TJOI2015]弦论 https://www.luogu.org/problemnew/show/P3975
- [BZOJ3277]串 https://www.lydsy.com/JudgeOnline/problem.php?id=3277
- [BZOJ3926][ZJOI2015]诸神眷顾的幻想乡
- [BZOJ2806][CTSC2012]熟悉的文章(Cheat) https://www.luogu.org/problemnew/show/P4022
- [BZOJ1396&2865]识别子串
- [HEOI2015]最短不公共子串 https://www.luogu.org/problemnew/show/P4112
- [BZOJ2555]SubString
- [BZOJ5137][Usaco2017 Dec]Standing Out from the Herd
- [BZOJ2780][SPOJ8093]Sevenk Love Oimaster https://www.luogu.org/problemnew/show/SP8093
- [CF700E]Cool Slogans https://www.luogu.org/problemnew/show/CF700E
- [CF666E]Forensic Examination
- [BZOJ5408][HN省队集训6.25T3]string https://www.lydsy.com/JudgeOnline/problem.php?id=5408