【后缀自动机】SPOJLCS SPOJNSUBSTR SPOJLCS2 HDU4416

来源:互联网 发布:网络公开课免费的吗 编辑:程序博客网 时间:2024/05/16 06:41

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

构建自动机

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

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

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

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

#include <iostream>#include <cstdio>#include <cstring>#define MAXN 250005using 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 500005using 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;}
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 自考挂科10门了怎么办 高中的会考没过怎么办 毕业证和学位证丢了怎么办 大学毕业证学位证丢了怎么办 大学毕业证和学位证丢了怎么办 毕业证是士官证号码怎么办 自考本科准考证丢了怎么办 自考档案搞丢了怎么办 自考本科档案在自己手里怎么办? 本科自考准考证丢了怎么办 大学团员证丢了怎么办 大学开学团员证丢了怎么办 研究生开学没有团员证怎么办 研究生开学已经不是团员了怎么办 毕业了要搬宿舍怎么办 中专学历认证已停止怎么办 中专不做学历认证考试怎么办 大学生欠学费被扣毕业证怎么办 考警校体检没过怎么办 美国签证申请预约名字写错怎么办 当兵不从学校走怎么办 门牙崩了一小块怎么办 遇到很难过的事情怎么办 小孩子上课精力不集中怎么办 每天工作都很累压力大怎么办 重体力活搬不动怎么办 大学没参加体测怎么办 英文写的很丑怎么办 患有勃起障碍应该怎么办较好 运动过度小腿肌肉酸痛怎么办 高考有纹身是字怎么办 新生儿测听力没过关怎么办 色弱高考体检时没查出来怎么办 公司福利体检查二对半怎么办 高考体检表复印件丢了怎么办 高考体检表身高填错了怎么办 大学档案高考体检表丢了怎么办 工厂组织体检我有乙肝怎么办 我有乙肝单位组织体检怎么办? 矮腰袜子老掉怎么办 短腰袜子老下滑怎么办