后缀自动机

54 篇文章 0 订阅
27 篇文章 0 订阅

作用

常用于处理字符串问题,可以高效解决许多字符串问题。

实现方法

有点像将一个字符串的所有后缀都建在一个AC自动机上,但不同的是后缀自动机的节点数最多为2*n,因为它只记录需要记录的点,一些没有记录东西的点可以视为与下面有价值的节点并在一起,这样大大降低了时间复杂度和空间复杂度。
对于每一个节点记录它的后面加上每个字符后后缀最长可以到达的节点,以及它的长度len,它去掉一个最短前缀后存在的节点pre。
对于加入一个字符c,首先新开一个np,其长度为last节点的长度+1(上一次加入,此时长度最长的节点),然后沿着last节点的pre向上查找,直到根节点或是在该节点(命名为p)后面加上c后能匹配到节点q。
若为前者,则np的pre为根节点。
若为后者,则判断q的len是否为p的len+1。
如果是,说明q节点是与p直接相连的,而不是被合并在q节点的。
反之,则说明该节点被省略,新建节点nq,复制q点除了长度外的所有信息,nq的长度为p的长度+1,而q,np的pre都改为nq,接着沿着p的pre向上找,直到它在后面加上c后匹配到的节点不是q停止,将这些原来匹配到q的节点都改为加上c后匹配到nq.

代码

void add(char v)
{
    int np=++tt,q,nq,p=last,u=zh(v);
    node[np].len=node[p].len+1;
    last=np;
    for(; p!=-1&&node[p].to[u]==-1; p=node[p].pre)
        node[p].to[u]=np;
    if(p==-1)
    {
        node[np].pre=0;
        return;
    }
    q=node[p].to[u];
    if(node[q].len==node[p].len+1)
    {
        node[np].pre=q;
        return;
    }
    nq=++tt;
    memcpy(node[nq].to,node[q].to,sizeof(node[q].to));
    node[nq].pre=node[q].pre;
    node[nq].len=node[p].len+1;
    node[q].pre=node[np].pre=nq;
    for(; p!=-1&&node[p].to[u]==q; p=node[p].pre)
        node[p].to[u]=nq;
}

例题 poj 2217

此题也可以用高度数组来做,复杂度为O(n*(logn)^2),而后缀自动机要快的多,复杂度仅为O(n).

题意

求两个字符串的最长公共子串。

做法

首先对a串建后缀自动机,之后b串沿着后缀自动机匹配,匹配到一个答案加一,失败则沿着该节点的pre向上找,直到匹配上或是找到了根节点,答案改为此时节点的长度,匹配过程中的答案最大值即为最长公共子序列的长度。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#define N 20010
using namespace std;

int T,last,tt,ans;
string a,b;
struct Node
{
    int to[200],pre,len;
    Node()
    {
        memset(to,-1,sizeof(to));
        pre=-1;
        len=0;
    }
    void init()
    {
        memset(to,-1,sizeof(to));
        pre=-1;
        len=0;
    }
} node[20010];

inline int zh(char u)
{
    return u;
}

void add(char v)
{
    int np=++tt,q,nq,p=last,u=zh(v);
    node[np].len=node[p].len+1;
    last=np;
    for(; p!=-1&&node[p].to[u]==-1; p=node[p].pre)
        node[p].to[u]=np;
    if(p==-1)
    {
        node[np].pre=0;
        return;
    }
    q=node[p].to[u];
    if(node[q].len==node[p].len+1)
    {
        node[np].pre=q;
        return;
    }
    nq=++tt;
    memcpy(node[nq].to,node[q].to,sizeof(node[q].to));
    node[nq].pre=node[q].pre;
    node[nq].len=node[p].len+1;
    node[q].pre=node[np].pre=nq;
    for(; p!=-1&&node[p].to[u]==q; p=node[p].pre)
        node[p].to[u]=nq;
}

void find()
{
    int now=0,i,t,u,res=0;
    for(i=0,t=b.size(); i<t; i++)
    {
        u=zh(b[i]);
        for(; now&&node[now].to[u]==-1;now=node[now].pre,res=node[now].len);
        if(node[now].to[u]!=-1)
        {
            res++;
            now=node[now].to[u];
        }
        ans=max(ans,res);
    }
}

int main()
{
    int i,j,t;
    char ch;
    cin>>T;
    ch=getchar();
    while(T--)
    {
        getline(cin,a);
        getline(cin,b);
        last=0;
        for(i=0; i<=tt; i++) node[i].init();
        tt=0;
        for(i=0,t=a.size(); i<t; i++)
        {
            add(a[i]);
        }
        ans=0;
        find();
        printf("Nejdelsi spolecny retezec ma delku %d.\n",ans);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值