后缀自动机

后缀自动机学习


后缀自动机就是能识别一个串的所有后缀,附加功能是可以识别所有字串,还能统计字串个数。

后缀自动机的parent是指该节点的父亲,len表示这个节点能表示的最长字串的长度

child[][]表示该节点添加一个字符能到达的状态。。




spoj1811 求两个串的最长公共字串

解法:用a串建立后缀自动机,然后用b串在自动机上跑,其中parent数组记录节点的父亲,len记录该节点能表示的最大字串长度

定义长度为num,如果遇到字符x存在孩子child[u]那么num++,否则找父亲节点,直到找到有child[u]的节点

同时令num=len[u]+1(父亲节点的len值一定是跟从根到该节点长度一致的的)

如果失配了,也就是访问到根,那么num=0,不断记录num的最大值即可

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn  510000
struct Auto_Sufix{
    int parent[maxn];
    int child[maxn][26];
    int len[maxn];
    int last;
    int cnt;
    int newNode(){
        memset(child[cnt],0,sizeof(child[cnt]));
        len[cnt] = parent[cnt] = 0;
        return cnt++;
    }
    void init(){
        last  = cnt = 1;
        newNode();
    }
    void add(int x){
        int np = newNode(),pa = last;
        len[np] = len[last]+1;
        while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa];
        if(pa == 0) parent[np] = 1;
        else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x];
        else {
            int nq = newNode(),p=child[pa][x];
            memcpy(child[nq],child[p],sizeof(child[p]));
            len [nq] = len[pa]+1;
            parent[nq] = parent[p];
            parent[np] = parent[p] = nq;
            for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]);
        }
        last = np;
    }
    int getmore(){
        int pa = parent[last];
        return len[last]-len[pa];
    }

    int getMaxCommon(char*word){
        int len1 = strlen(word);
        int ans = 0,num=0,u=1,x;
        for(int i = 0;i < len1; i++){
            x = word[i]-'a';
            if(child[u][x] != 0 ){
                num++,u=child[u][x];
            }
            else {
                while(u&&!child[u][x]) u=parent[u];
                if(u==0)u=1,num=0;
                else num=len[u]+1,u=child[u][x];
            }
            ans=max(ans,num);
        }
        return ans;
    }
};


Auto_Sufix sufix;
char word[maxn];
int main(){
    while(scanf("%s",word)!=EOF){
        sufix.init();
        int len = strlen(word);
        for(int i = 0;i < len ;i++)
            sufix.add(word[i]-'a');
        scanf("%s",word);
        int ans= sufix.getMaxCommon(word);
        printf("%d\n",ans);
    }
    return 0;
}
spoj1812 求多个串的最长公共字串

跟上一题类似,先建立一个自动机,然后用其他串在上面跑

用ans数组记录当前串到每个状态得到的最大长度,用原来的自动机的len记录所有串到达这些状态的公共长度的最大值

但是在跟新len值的时候需要注意:因为一个状态能表示的长度大于其父亲状态的长度,如果到达这个状态了,说明其

父亲状态也是可达,同时父亲的父亲。。。也是可达的。所以还需要跟新父亲状态的ans值

因为后缀自动机的节点是一个拓扑图,没有环路的有向图,根据定义len越大的状态能表示的字串越多,其父亲的状态的len

必然小于他的len值,对这些状态的len值拍个序,就能实现逆拓扑序了,采用基数排序,可以做到o(n)的排序

然后用len值最大的节点跟新len,在用这个节点跟新该父亲的ans即可

top[i]记录的就是len值排在i位的节点的下标

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn  210000
struct Auto_Sufix{
    int parent[maxn];
    int child[maxn][26];
    int len[maxn];
    int last;
    int cnt;
    int newNode(){
        memset(child[cnt],0,sizeof(child[cnt]));
        len[cnt] = parent[cnt] = 0;
        return cnt++;
    }
    void init(){
        last  = cnt = 1;
        newNode();
    }
    void add(int x){
        int np = newNode(),pa = last;
        len[np] = len[last]+1;
        while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa];
        if(pa == 0) parent[np] = 1;
        else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x];
        else {
            int nq = newNode(),p=child[pa][x];
            memcpy(child[nq],child[p],sizeof(child[p]));
            len [nq] = len[pa]+1;
            parent[nq] = parent[p];
            parent[np] = parent[p] = nq;
            for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]);
        }
        last = np;
    }
    int getmore(){
        int pa = parent[last];
        return len[last]-len[pa];
    }

};
Auto_Sufix sufix;
char word[maxn];
int ans[maxn];
int top[maxn];
int main(){
    gets(word);
    sufix.init();
    int len = strlen(word);
    for(int i = 0;i < len ;i++)
        sufix.add(word[i]-'a');
    memset(ans,0,sizeof(ans));

    int t = 0;

    for(int i = 0;i < sufix.cnt;i++) ans[i]=0;
    for(int i = 0;i < sufix.cnt;i++) ans[sufix.len[i]]++;
    for(int i = 1;i < sufix.cnt;i++) ans[i] += ans[i-1];
    for(int i = sufix.cnt-1;i>0;i--) top[--ans[sufix.len[i]]]=i;
    memset(ans,0,sizeof(ans));

    while(gets(word)){
        int len1 = strlen(word);
        int num=0,u=1,x;
        for(int i = 0;i < len1; i++){
            x = word[i]-'a';
            if(sufix.child[u][x] != 0 ){
                num++,u=sufix.child[u][x];
            }
            else {
                while(u&&!sufix.child[u][x]) u=sufix.parent[u];
                if(u==0)u=1,num=0;
                else num=sufix.len[u]+1,u=sufix.child[u][x];
            }
            ans[u] = max(ans[u],num);
        }
        for(int i = sufix.cnt-1;i>1;i--){
            u = top[i];
            sufix.len[u] = min(sufix.len[u],ans[u]);
            ans[sufix.parent[u]] = max(ans[sufix.parent[u]],ans[u]);
            ans[u] = 0;
        }
    }
    int res  = 0 ;
    for(int i = 2;i < sufix.cnt;i++)
        res = max(res,sufix.len[i]);
    printf("%d\n",res);
    return 0;
}

hdu 4416Good Article Good sentence

求一个字符串A 的所有不重复字串的个数,同时这些子串不出现在s1,s2,.....sn中

同样用A建立一个自动机,然后开一个len1数组,记录的是s1到sn在这个自动机上跑能得到字串长度的最大值

同样用逆拓扑序遍历最后生成的len1数组,且需要跟新每个节点的父亲节点。记录len1[u]-len[parenrt[u]]]表示到达的这个状态最多可以表示

几个字串,可能为负数,因为不可达,那么取0和该值得最大值。

父亲节点的值为len1[u]和len[paren[u]]的最小值,防止重复计算

然后用a串的字串数-重复的字串数就是答案,可能超int

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200001
int parent[maxn],child[maxn][26],len[maxn],len1[maxn];
int cnt,last;
int newNode(){
    memset(child[cnt],0,sizeof(child[cnt]));
    len[cnt] = parent[cnt] = 0;
    return cnt++;
}
void init(){
    cnt = last = 1;
    newNode();
}

void add(int x){
    int pa = last, np = newNode();
    len[np] = len[pa]+1;
    last = np;
    while(pa && !child[pa][x]) child[pa][x] = np,pa=parent[pa];
    if(pa == 0) parent[np] = 1;
    else if(len[child[pa][x]] == len[pa]+1) parent[np] = child[pa][x];
    else {
        int nq = newNode(), p = child[pa][x];
        memcpy(child[nq],child[p],sizeof(child[nq]));
        parent[nq] = parent[p];
        len[nq] = len[pa]+1;
        parent[p] = parent[np] = nq;
        while(pa && child[pa][x] == p) child[pa][x]=nq,pa=parent[pa];
    }
}
#define ll long long
ll getmore(){
    ll ans = 0;
    for(int i = 2;i < cnt;i++)
        ans += len[i]-len[parent[i]];
    return ans;
}

char word[maxn];
int bucket[maxn],top[maxn];
int main(){
    int t,n,lenth;
    scanf("%d",&t);
    for(int tt = 1; tt <= t; tt++){
        scanf("%d\n",&n);
        gets(word);
        lenth = strlen(word);
        init();
        for(int i = 0;i < lenth; i++)
            add(word[i]-'a');
        ll ans = getmore();
        for(int i = 0;i < cnt; i++)len1[i] = 0;

        while(n--){
            gets(word);
            lenth = strlen(word);
            int u=1,x,sum=0;
            for(int i = 0;i < lenth; i++){
                x = word[i]-'a';
                if(child[u][x] != 0) u = child[u][x],sum++;
                else {
                    while(u&&child[u][x]==0)u=parent[u];
                    if(u == 0) u=1,sum=0;
                    else sum=len[u]+1,u=child[u][x];
                }
                len1[u] = max(sum,len1[u]);
            }
        }
        for(int i = 0;i < cnt;i++)bucket[i] = 0;
        for(int i = 0;i < cnt;i++)bucket[len[i]]++;
        for(int i = 1;i < cnt;i++)bucket[i]+=bucket[i-1];
        for(int i = cnt-1;i >=0;i--)top[--bucket[len[i]]]=i;
        long long  res = 0;
        int u,n;
        for(int i = cnt-1;i > 1;i--){
            u = top[i];
//            if(len1[u] == 0) res+=len[u]-len[parent[u]];
//            else if(len1[u]<len[u])res+=len[u]-len1[u];
//            len1[parent[u]] = max(len1[parent[u]],len1[u]);
            n = max(0,len1[u]-len[parent[u]]);
            res+=n;
            if(parent[u])len1[parent[u]] = min(len[parent[u]],max(len1[u],len1[parent[u]]));
        }
        printf("Case %d: %I64d\n",tt,ans-res);

    }
    return 0;
}




hdu 4622Reincarnation多校题

给一个字符串,给一些询问求在某一个区间字串个数

后缀自动机枚举不同起点,ans[i][j]表示从i到j的字串个数

然后在线回答即可。离线回答可能快一些,可以减去一些不必要的起点

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 4007
struct Auto_Sufix{
    int parent[maxn];
    int child[maxn][26];
    int len[maxn];
    int last;
    int cnt;
    int newNode(){
        memset(child[cnt],0,sizeof(child[cnt]));
        len[cnt] = parent[cnt] = 0;
        return cnt++;
    }
    void init(){
        last = cnt = 1;
        newNode();
    }
    void add(int x){
        int np = newNode(),pa = last;
        len[np] = len[last]+1;
        last = np;

        while(pa && !child[pa][x])child[pa][x]=np,pa=parent[pa];
        if(pa == 0) parent[np] = 1;
        else if(len[pa]+1==len[child[pa][x]]) parent[np]=child[pa][x];
        else {
            int nq = newNode(),p=child[pa][x];
            memcpy(child[nq],child[p],sizeof(child[p]));
            len [nq] = len[pa]+1;
            parent[nq] = parent[p];
            parent[np] = parent[p] = nq;
            for(;pa&&child[pa][x]==p;child[pa][x]=nq,pa=parent[pa]);
        }
    }
    int getmore(){
        int pa = parent[last];
        return len[last]-len[pa];
    }
};


Auto_Sufix sufix;
char word[maxn];
int ans[maxn][maxn];
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%s",word);
        memset(ans,0,sizeof(ans));
        int n = strlen(word);
        for(int i = 0;i < n; i++){
            sufix.init();
            for(int j = i;j < n; j++){
                sufix.add(word[j]-'a');
                ans[i][j] = ans[i][j-1]+sufix.getmore();
            }
        }
        int q,b,t;
        scanf("%d",&q);
        while(q--){
            scanf("%d%d",&b,&t);
            printf("%d\n",ans[b-1][t-1]);
        }

    }
    return 0;
}




hdu 4436 str2int

给一些数字字符串,求这些字符串的字串(不重复的,没有前缀0)的和。

将这些字符串用一个连接符连接起来,然后建立后缀自动机

然后按照len值的大小排序

因为不能有前缀0,所以根节点不能访问0的孩子节点

用sum[u]标记u这个所有能到这个点的数值之和,num[u]表示有多少个路径到这里

那么sum[u]就是以u结束的数的和然后转移的时候,sum[u]*10+num[u]*j      j表示后面接的数字

所有合法的sum[u]之和就是答案

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 200007
#define mod 2012
int parent[maxn],child[maxn][11],len[maxn];
int cnt,last;
int newNode(){
    memset(child[cnt],0,sizeof(child[cnt]));
    len[cnt] = parent[cnt] = 0;
    return cnt++;
}
void init(){
    cnt = last = 1;
    newNode();
}

void add(int x){
    int pa = last, np = newNode();
    len[np] = len[pa]+1;
    last = np;
    while(pa && !child[pa][x]) child[pa][x] = np,pa=parent[pa];
    if(pa == 0) parent[np] = 1;
    else if(len[child[pa][x]] == len[pa]+1) parent[np] = child[pa][x];
    else {
        int nq = newNode(), p = child[pa][x];
        memcpy(child[nq],child[p],sizeof(child[nq]));
        parent[nq] = parent[p];
        len[nq] = len[pa]+1;
        parent[p] = parent[np] = nq;
        while(pa && child[pa][x] == p) child[pa][x]=nq,pa=parent[pa];
    }
}

char word[maxn];
int bucket[maxn],top[maxn];
int num[maxn],sum[maxn];
int main(){
    int t;
    while(scanf("%d",&t)!=EOF){
        int len1 = 0;
        for(int i = 0;i < t;i++){
            scanf("%s",&word[len1]);
            len1 += strlen(&word[len1]);
            word[len1++]='0'+10;
        }
        init();
        for(int i = 0;i < len1;i++)
            add(word[i]-'0');

        memset(bucket,0,sizeof(bucket));
        for(int i = 1;i < cnt; i++)bucket[len[i]]++;
        for(int i = 1;i < cnt; i++)bucket[i]+=bucket[i-1];
        for(int i = cnt-1;i > 0;i--)top[--bucket[len[i]]]=i;

        memset(num,0,sizeof(num));
        memset(sum,0,sizeof(sum));
        num[1]=1;
        int ans = 0,v,res,u,j;
        for(int i = 0;i<cnt-1;i++){
            u = top[i];
            for(j = 0;j < 10;j++){
                if(j == 0 && u == 1) continue;
                if(child[u][j]==0)continue;

                v = child[u][j];
                num[v]+=num[u];
                if(num[v] >= mod)num[v]-=mod;
                res = sum[u]*10 + num[u]*j;
                sum[v]+=res;
                sum[v]%=mod;
            }
            ans+=sum[u];
            if(ans >= mod)ans-=mod;
        }
        ans%=mod;
        printf("%d\n",ans);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GDRetop

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值