[kuangbin]后缀自动机题解
[kuangbin]后缀自动机题解
学了后缀自动机,推荐几个比较好的学习资料:
- clj老师的NOI现场讲稿PPT,满大街都是随便找,很详细但是不是很好懂
- Menci大佬的博客,讲解上更数学(这是好事.jpg),而且还有很好的配图方便理解
- 经典俄文教程的翻译,讲解很好懂,不过读起来就有点像外国教材那种废话很多的感觉……
SPOJ - LCS
题意
最长公共子串,字符串长度$ \le 250000$,字符集为小写字母,时限294 ms
解题思路
用后缀数组的话,可以用非法字符连接AB后建出SA,求出LCP最大值即可,复杂度\(O(N)\) (DC3)
不确定能不能过,因为DC3不太好写就不试了,反正是为了练后缀自动机
用SAM的话,可以对A串建出SAM,然后让B串在SAM上运行,如果某次转移\(c\)失配,那么就沿着后缀连接转移,直到当前节点可以匹配\(c\)。在这个过程中维护匹配的子串长度,如果成功转移就\(+1\),沿着后缀连接移动就变成当前节点的\(max\)值(节点表示的字符串的最大长度)
但是这个时限实在是太紧了,而且SPOJ很慢……,需要很优秀的SAM实现才能过这题,我使用Menci的用动态申请点实现SAM的板子怎么都过不了,看来必须静态写法才能过这题,最后在博客上当了一份比较好的SAM实现:
用了不少优秀的技巧,比如重载new
运算符实现内存池,用指针遍历字符串来少求一次strlen
等,改造了一下
你这个模板不错,但马上就是我的了.jpg
AC代码
#include <bits/stdc++.h>
using namespace std;
const int SIGMA_SIZE=28;
const int MAXN=250009;
struct Node
{
static Node buf[],*bufp;
void* operator new (size_t) {return bufp++;}
int maxl;
Node *ch[SIGMA_SIZE],*slink;
Node()=default;
Node(int _maxl):maxl(_maxl){
fill(ch,ch+SIGMA_SIZE,nullptr);
slink=nullptr;
}
}Node::buf[MAXN*4],*Node::bufp=buf,*root,*last;
void init()
{
last=root=new Node(0);
}
Node* extend(char c)
{
int x=c-'a';
Node *u=new Node(last->maxl+1),*v;
for (v=last;v&&!v->ch[x];v=v->slink) v->ch[x]=u;
if(!v)
u->slink=root;
else if(v->ch[x]->maxl==v->maxl+1)
u->slink=v->ch[x];
else
{
Node *n = new Node(v->maxl+1),*o=v->ch[x];
copy(o->ch,o->ch+SIGMA_SIZE,n->ch);
n->slink=o->slink;
o->slink=u->slink=n;
for(;v&&v->ch[x]==o;v=v->slink) v->ch[x]=n;
}
last=u;
return u;
}
void build(char *s)
{
init();
for (char* i=s;*i;i++)
extend(*i);
}
int solve(char *s)
{
Node *run=root;
int res=0,nowv=0;
for (char *i=s;*i;i++)
{
int x=*i-'a';
for (;run&&!run->ch[x];run=run->slink)
;
if(!run)
{
run=root;
nowv=0;
}
else
{
nowv=min(run->maxl,nowv)+1;
res=max(res,nowv);
run=run->ch[x];
}
}
return res;
}
char A[MAXN],B[MAXN];
int main()
{
scanf("%s",&A);scanf("%s",&B);
build(A);
printf("%d\n",solve(B));
return 0;
}
SPOJ - LCS2
题意
对于长度不超过\(100000\)的若干个串,求LCS,串的数量\(\le 10\),时限236 ms
解题思路
这题不用数组实现SAM根本过不了,这次连new
都不让用了,SPOJ时限真的是屑,卡常数有什么用
放弃了我自己的倔强用数组实现,真香.jpg
这题的思路和上一题比较类似,不过需要记录每个串在SAM上某个状态的匹配长度,取最小值,最后取最大值
不过有一个要注意的点,如果一个状态能够匹配,那么它所有后缀链接树上的祖先都能匹配,但是在转移过程中不一定能保证更新到所有的祖先,如果不更新会导致错误。
更新祖先没有必要递归做,考虑SAM的性质,对\(max\)做排序就是拓扑序,这里用计数排序降低复杂度
代码中的\(maxmatch\)表示该节点在所有串的匹配中最大都匹配的长度,\(nowmatch\)是这次匹配的长度,显然\(maxmatch\)不超过每个节点的\(max\)值
\(160ms\)过的,某种程度上有点绝望……
AC代码
#include <bits/stdc++.h>
using namespace std;
const int SIGMA_SIZE=26;
const int MAXN=100007;
const int inf=0x3f3f3f3f;
struct Node
{
int maxl,slink,ch[SIGMA_SIZE];
int maxmatch,nowmatch;
Node()
{
maxl=slink=nowmatch=0;
maxmatch=inf;
memset(ch,0,sizeof(ch));
};
}node[MAXN<<1];
int tot,last,root,topo[MAXN<<1],buc[MAXN<<1];
int new_node(int x)
{
node[++tot].maxl=x;
return tot;
}
void init()
{
tot=0;
root=last=new_node(0);
}
void extend(char c)
{
int x=c-'a';
int p=last,np=new_node(node[p].maxl+1);
for (;p&&!node[p].ch[x];p=node[p].slink) node[p].ch[x]=np;
if(!p) node[np].slink=root;
else
{
int q=node[p].ch[x];
if(node[q].maxl==node[p].maxl+1) node[np].slink=q;
else
{
int nq=new_node(node[p].maxl+1);
memcpy(node[nq].ch,node[q].ch,sizeof(node[q].ch));
node[nq].slink=node[q].slink;
node[q].slink=node[np].slink=nq;
for (;p&&node[p].ch[x]==q;p=node[p].slink) node[p].ch[x]=nq;
}
}
last=np;
}
void CountSort()
{
int maxlen=0;
for (int i=1;i<=tot;i++) {buc[node[i].maxl]++;maxlen=max(node[i].maxl,maxlen);}
for (int i=1;i<=maxlen;i++) buc[i]+=buc[i-1];
for (int i=1;i<=tot;i++) topo[buc[node[i].maxl]--]=i;
}
void build(char *s)
{
init();
for (char *c=s;*c;c++)
extend(*c);
CountSort();
//for (int i=1;i<=tot;i++) node[i].maxmatch=node[i].maxl;
}
void match(char *s)
{
int run=root,nowv=0;
for (char *c=s;*c;c++)
{
int x=*c-'a';
if(node[run].ch[x]){
run=node[run].ch[x];
node[run].nowmatch=max(node[run].nowmatch,++nowv);
}else{
while(run&&!node[run].ch[x]) run=node[run].slink;
if(!run){
run=root;
nowv=0;
}else{
nowv=node[run].maxl;
run=node[run].ch[x];
node[run].nowmatch=max(node[run].nowmatch,++nowv);
}
}
}
}
char str[MAXN];
int main()
{
scanf("%s",str);
build(str);
while(scanf("%s",str)!=EOF)
{
match(str);
for (int i=tot;i>=1;i--)
{
int u=topo[i];
node[u].maxmatch=min(node[u].nowmatch,node[u].maxmatch);
if(node[u].slink)
node[node[u].slink].nowmatch=max(node[node[u].slink].nowmatch,min(node[node[u].slink].maxl,node[u].nowmatch));
node[u].nowmatch=0;
}
}
int ans=0;
for (int i=1;i<=tot;i++) ans=max(ans,node[i].maxmatch);
printf("%d\n",ans);
}