[kuangbin]后缀自动机题解

[kuangbin]后缀自动机题解

学了后缀自动机,推荐几个比较好的学习资料:

  1. clj老师的NOI现场讲稿PPT,满大街都是随便找,很详细但是不是很好懂
  2. Menci大佬的博客,讲解上更数学(这是好事.jpg),而且还有很好的配图方便理解
  3. 经典俄文教程的翻译,讲解很好懂,不过读起来就有点像外国教材那种废话很多的感觉……

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);
}

转载于:https://www.cnblogs.com/falseangel/p/9016013.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值