clj大佬论文
自己对于后缀自动机的理解:每一个节点代表的是具有相同的right集合的子串(right集合的定义具体去看clj大佬的论文),整个最后构成的有向无环图,可以看成是一棵树,step[i]代表的是right[i]中子串的最长长度也就是论文中所说的Max(i),pre[i]代表的是i节点在树中的父节点,这里和AC自动机里的fail指针有点类似,往上找就相当于找到的是当前串的后缀在SAM中出现过的最长长度。
spoj 1811
题意:给两个串,求最长公共子串
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
char s[maxn],s1[maxn];
int son[maxn][26],pre[maxn],step[maxn],last,total,root;//son儿子边是否存在 pre可以理解为parent树上的父节点 step到达i节点走的最长步数
inline void push_back(int v)
{
step[++total]=v;
}
void Extend(char ch)
{
push_back(step[last]+1);//新建ch的节点
int p=last,np=total;
for (;!son[p][ch]; p=pre[p]) son[p][ch]=np;//找到有ch出边的节点
if (!p) pre[np]=root;//如果都没有则指向root
else//按照论文里面的方法处理
{
int q=son[p][ch];
if (step[q]!=step[p]+1)
{
push_back(step[p]+1);
int nq=total;
memcpy(son[nq],son[q],sizeof(son[q]));
pre[nq]=pre[q];
pre[q]=pre[np]=nq;
for (; p!=-1&&son[p][ch]==q; p=pre[p]) son[p][ch]=nq;
}
else pre[np]=q;
}
last=np;
}
void Build()
{
root=total=last=1;
memset(son,0,sizeof(son));
memset(pre,0,sizeof(pre));
memset(step,0,sizeof(step));
int len=strlen(s);
for (int i=0; i<len; i++) Extend(s[i]-'a');
}
void debug()
{
for(int i = 1; i <= total; ++i)
{
printf("id=%d, fa=%d, step=%d, ch=[ ", i, pre[i], step[i]);
for(int j = 0; j < 26; ++j)
{
if(son[i][j])
printf("%c,%d ", j+'a',son[i][j]);
}
puts("]");
}
}
int main()
{
while(~scanf("%s%s",s,s1))
{
Build();
//debug();
int ans=0;
int p=root,len=strlen(s1),tmp=0;
for(int i=0; i<len; i++)//用s1在后缀自动机上跑
{
if(son[p][s1[i]-'a'])//p节点有s1[i]的出边
p=son[p][s1[i]-'a'],tmp++;
else//没有就找出现过的最长后缀
{
while(p&&son[p][s1[i]-'a']==0)//找到最长的在s中出现过得后缀
p=pre[p];
if(p)//有则从这个节点继续往下匹配
{
tmp=step[p]+1;
p=son[p][s1[i]-'a'];
}
else//没有就从根重新开始
p=root,tmp=0;
}
ans=max(ans,tmp);//更新答案
}
printf("%d\n",ans);
}
}
对于后缀自动机的理解
1.在parent树上某一节点的pre指向的节点代表的是可以和当前节点接收同样的后缀的状态节点。
2.parent树父节点的状态代表的子串是子节点的最长后缀。
3.sam中的转移可以直接对应所有后缀的开头。
4.出现次数向父亲传递,接收串数从儿子获取。