【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416

据说后缀自动机可以替代后缀数组和后缀树……
后缀自动机,用线性的节点数来保存所有的后缀。

构建自动机

struct node
{
    int ch[26], len, link;
    void init()
    {
        len=link=0;
        memset(ch,0,sizeof ch);
    }
}tree[MAXN<<1];

int pos;
char w[MAXN];

void add(char v)
{
    int now=++pos;
    v-='a';
    tree[now].len=tree[last].len+1;
    tree[last].ch[v]=now;
    int p;
    for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link)
        tree[p].ch[v]=now;
    if(!p)tree[now].link=1;
    else
    {
        int q=tree[p].ch[v];
        if(tree[p].len+1==tree[q].len)
            tree[now].link=q;
        else
        {
            tree[++pos]=tree[q];
            tree[pos].len=tree[p].len+1;
            tree[now].link=tree[q].link=pos;
            while(p&&tree[p].link==q)
            {
                tree[p].ch[v]=pos;
                p=tree[p].link;
            }
        }
    }
    last=now;
}

void build(char a[])
{
    for(int i=1;i<=pos;++i)tree[i].init();
    int len=strlen(a);
    last=pos=1;
    for(int i=0;i<len;++i)
        add(a[i]);
}

后缀自动机有几条性质。

1.某两个节点 u v len(v)<=len(u) )是终点等价类当且仅当 v u 的后缀。
2.link指针组成了一棵以 root root 为虚拟节点)的树。儿子的终点集合是父亲的终点集合的子集。
3.某一个节点的终点集合元素个数是它的子树节点个数(以后缀边link组成的树)。若该节点不是原字符串的后缀,则减一。

其中,如何计算终点集合元素个数是后缀自动机的核心所在。

SPOJ - LCS
题目大意:给你两个字符串,求这两个字符串的最长公共子串(子串是连续的)。

首先对第一个字符串构建后缀数组,再把第二个字符串带入。

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 250005
using namespace std;

char a[MAXN], b[MAXN];
int ans, tmp, last, cur, cnt, root;

struct node
{
    int link, len, ch[26];
    void init()
    {
        link=len=0;
        memset(ch,0,sizeof ch);
    }
}tree[MAXN<<1];

void add(char a)
{
    int p=0, v=a-'a';
    tree[last].ch[v]=++cnt;
    tree[cnt].len=tree[last].len+1;
    cur=cnt;
    for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link)tree[p].ch[v]=cur;
    if(!p)tree[cur].link=root;
    else
    {
        int q=tree[p].ch[v];
        if(tree[p].len+1==tree[q].len)tree[cur].link=q;
        else
        {
            tree[++cnt]=tree[q];
            tree[cnt].len=tree[p].len+1;
            tree[cur].link=tree[q].link=cnt;
            while(p&&tree[p].ch[v]==q)
            {
                tree[p].ch[v]=cnt;
                p=tree[p].link;
            }
        }
    }
    last=cur;
}

void build(char a[])
{
    for(int i=1;i<=cnt;++i)tree[i].init();
    cnt=last=cur=root=1;
    int len=strlen(a);
    for(int i=0;i<len;++i)
        add(a[i]);
}

int main()
{
    int len, p, v;
    while(~scanf("%s%s",a,b))
    {
        build(a);
        len=strlen(b);
        ans=tmp=0, p=root;
        for(int i=0;i<len;++i)
        {
            v=b[i]-'a';
            if(tree[p].ch[v])
            {
                p=tree[p].ch[v];
                ++tmp;
            }
            else
            {
                while(p&&!tree[p].ch[v])
                    p=tree[p].link;
                if(!p)
                    p=root, tmp=0;
                else
                        tmp=tree[p].len+1, p=tree[p].ch[v];
            }
            ans=max(ans,tmp);
        }
        printf("%d\n",ans);
    }
    return 0;
}

SPOJNSUBSTR
题目大意:对一个给定字符串s,求 F(i)(1<=i<=len(s)) 。其中 F(i) 表示长度为i的子串出现的最多次数。

首先对字符串s构建一个后缀自动机。之后遍历由后缀边组成的树。
F(i+1) 去更新 F(i)

注意:把MAXN开大一倍……

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 500005
using namespace std;

struct node
{
    int link, ch[26], len, cnt;
    void init()
    {
        len=link=0;
        memset(ch,0,sizeof ch);
    }
}tree[MAXN<<1];

char w[MAXN];
int last, pos;
int cnt[MAXN], F[MAXN], len, r[MAXN];

void add(char a)
{
    int now=++pos, v=a-'a', p;
    tree[last].ch[v]=now;
    tree[now].len=tree[last].len+1;
    for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link)
        tree[p].ch[v]=now;
    if(!p)tree[now].link=1;
    else
    {
        int q=tree[p].ch[v];
        if(tree[p].len+1==tree[q].len)tree[now].link=q;
        else
        {
            tree[++pos]=tree[q];
            tree[pos].len=tree[p].len+1;
            tree[now].link=tree[q].link=pos;
            while(p&&tree[p].ch[v]==q)
            {
                tree[p].ch[v]=pos;
                p=tree[p].link;
            }
        }
    }
    last=now;
}

void build(char a[])
{
    for(int i=1;i<=pos;++i)tree[i].init();
    last=pos=1;
    for(int i=0;i<len;++i)
        add(a[i]);
}

int main()
{
    scanf("%s",w);
    len=strlen(w);
    build(w);

    for(int i=1;i<=pos;++i)++cnt[tree[i].len];
    for(int i=1;i<=len;++i)cnt[i]+=cnt[i-1];
    for(int i=1;i<=pos;++i)r[cnt[tree[i].len]--]=i;

    int p=1;
    for(int i=0;i<len;++i)
        p=tree[p].ch[w[i]-'a'], ++tree[p].cnt;
    int tmp;
    for(int i=pos;i>0;--i)
    {
        tmp=r[i];
        F[tree[tmp].len]=max(F[tree[tmp].len],tree[tmp].cnt);
        if(tree[tmp].link)tree[tree[tmp].link].cnt+=tree[tmp].cnt;
    }


    for(int i=len-1;i>0;--i)F[i]=max(F[i],F[i+1]);
    for(int i=1;i<=len;++i)printf("%d\n",F[i]);
    return 0;
}

SPOJ-LCS2
这道题是SPOJ-LCS的扩展版,即求多个字符串的最长公共子串。

在SPOJ-LCS中加上一个变量,跟着字符串更新即可。

然而蒟蒻TLE了几次,原因是木有把标记清零= =

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 200005
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;

struct node
{
    int ch[26], len, link;
    void init()
    {
        len=link=0;
        memset(ch,0,sizeof ch);
    }
}tree[MAXN<<1];

int pos, last, ans[MAXN<<1], cnt[MAXN], r[MAXN], temp[MAXN<<1];
char w[MAXN];

void add(int v)
{
    int now=++pos, p;
    tree[last].ch[v]=now;
    tree[now].len=tree[last].len+1;
    for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link)
        tree[p].ch[v]=now;
    if(!p)tree[now].link=1;
    else
    {
        int q=tree[p].ch[v];
        if(tree[p].len+1==tree[q].len)tree[now].link=q;
        else
        {
            tree[++pos]=tree[q];
            tree[pos].len=tree[p].len+1;
            tree[now].link=tree[q].link=pos;
            while(p&&tree[p].ch[v]==q)
            {
                tree[p].ch[v]=pos;
                p=tree[p].link;
            }
        }
    }
    last=now;
}

void build(char a[])
{
    for(int i=1;i<=pos;++i)tree[i].init();
    int len=strlen(a);
    last=pos=1;
    for(int i=0;i<len;++i)
        add(a[i]-'a');
}

int main()
{
    scanf("%s",w);
    build(w);

    int len, v, p, tmp;
    len=strlen(w);
    for(int i=1;i<=pos;++i)++cnt[tree[i].len];
    for(int i=1;i<=len;++i)cnt[i]+=cnt[i-1];
    for(int i=pos;i>0;--i)r[cnt[tree[i].len]--]=i;
    for(int i=1;i<=pos;++i)ans[i]=tree[i].len;

    while(~scanf("%s",w))
    {
        len=strlen(w);
        p=1, tmp=0;
        for(int i=0;i<len;++i)
        {
            v=w[i]-'a';
            if(tree[p].ch[v])
                p=tree[p].ch[v], ++tmp;
            else
            {
                while(p&&!tree[p].ch[v])p=tree[p].link;
                if(!p)p=1, tmp=0;
                else
                    tmp=tree[p].len+1, p=tree[p].ch[v];
            }
            temp[p]=max(temp[p],tmp);
        }
        for(int i=pos;i>0;--i)
        {
            v=r[i];
            ans[v]=min(ans[v],temp[v]);
            if(tree[v].link&&temp[v])
            {
                int q=tree[v].link;
                temp[q]=min(tree[q].len,max(temp[q],temp[v]));
            }
            temp[v]=0;//清空!!!
        }
    }
    int out=0;
    for(int i=1;i<=pos;++i)out=max(out,ans[i]);
    printf("%d\n",out);
    return 0;
}

HDU4416
题目大意:求是A串的子串但是不是B串的子串的个数。

后缀自动机的一个经典运用。
对A构建自动机,把B串代入自动机进行匹配,记录最长匹配长度。最后统计答案即可。具体步骤如下:
1.对于当前节点 u ,用 ans[u] 更新 ans[tree[u].link]
2.如果 tree[u].len>ans[u] ,说明在节点 u 中,长度 [ans[u]+1,tree[u].len] 的子串不是B串的子串。
3.如果 ans[u]==0 ,说明节点 u 中的子串均满足条件,但节点 u 中的子串为 tree[u].lentree[tree[u].link].len

这样这道题就简单了许多,而且实现细节上木有什么坑点。

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 100005
#define LL long long int
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;

char w[MAXN];
int n, cnt, last, len;
int c[MAXN], q[MAXN<<1], ans[MAXN<<1];

struct node
{
    int len, ch[26], link;
    void init()
    {
        len=link=0;
        memset(ch,0,sizeof ch);
    }
}tree[MAXN<<1];

void insert(int v)
{
    int now=++cnt;
    tree[now].len=tree[last].len+1;
    tree[last].ch[v]=now;
    int p;
    for(p=tree[last].link;p&&!tree[p].ch[v];p=tree[p].link)
        tree[p].ch[v]=now;
    if(!p)tree[now].link=1;
    else
    {
        int q=tree[p].ch[v];
        if(tree[q].len==tree[p].len+1)tree[now].link=q;
        else
        {
            int tmp=++cnt;
            tree[tmp]=tree[q];
            tree[tmp].len=tree[p].len+1;
            tree[now].link=tree[q].link=tmp;
            while(p&&tree[p].ch[v]==q)
            {
                tree[p].ch[v]=tmp;
                p=tree[p].link;
            }
        }
    }
    last=now;
}

void build(char w[])
{
    for(int i=0;i<=cnt;++i)tree[i].init(), ans[i]=0;
    last=cnt=1;
    len=strlen(w);
    for(int i=0;i<len;++i)insert(w[i]-'a');

    memset(c,0,sizeof c);
    for(int i=1;i<=cnt;++i)++c[tree[i].len];
    for(int i=1;i<=len;++i)c[i]+=c[i-1];
    for(int i=1;i<=cnt;++i)q[c[tree[i].len]--]=i;
}

void query(char w[])
{
    int len=strlen(w), p=1, v, tmp=0;
    for(int i=0;i<len;++i)
    {
        v=w[i]-'a';
        if(tree[p].ch[v])
        {
            ++tmp;
            p=tree[p].ch[v];
        }
        else
        {
            while(p&&!tree[p].ch[v])p=tree[p].link;
            if(!p)p=1, tmp=0;
            else{tmp=tree[p].len+1;p=tree[p].ch[v];}
        }
        ans[p]=max(ans[p],tmp);
    }

}

LL solve()
{
    LL tot=0;
    int v;
    for(int i=cnt;i>0;--i)
    {
        v=q[i];
        if(ans[v])
        {
            ans[tree[v].link]=max(ans[tree[v].link],ans[v]);
            if(tree[v].len>ans[v])tot+=tree[v].len-ans[v];
        }
        else tot+=tree[v].len-tree[tree[v].link].len;
    }
    return tot;
}

int main()
{
    int cas, CNT=0;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%s",&n,w);
        build(w);
        while(n--)
        {
            scanf("%s",w);
            query(w);
        }
        printf("Case %d: %I64d\n",++CNT,solve());
    }
    return 0;
}

你可能感兴趣的:(后缀自动机)