码迷,mamicode.com
首页 > 其他好文 > 详细

后缀自动机初探

时间:2016-01-19 09:05:03      阅读:189      评论:0      收藏:0      [点我收藏+]

标签:

之前看过几次后缀自动机,然后因为人太蠢都没看懂。

最近重新填坑TAT。。。

BZOJ4032: [HEOI2015]最短不公共子串

建出后缀自动机和序列自动机,然后我们知道自动机上每一条路径都相当于一个子串(子序列),这样只要从根节点开始bfs一遍,找到A有而B没有的,那就是字典序最小的辣。

技术分享
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#define rep(i,l,r) for (int i=l;i<=r;i++)
#define down(i,l,r) for (int i=l;i>=r;i--)
#define clr(x,y) memset(x,y,sizeof(x))
#define maxn 4005
#define ll long long
#define inf int(1e9)
using namespace std;
char sa[maxn],sb[maxn];
int f[maxn][maxn];
int n,m;
int read(){
    int x=0,f=1; char ch=getchar();
    while (!isdigit(ch)) {if (ch==-) f=-1; ch=getchar();}
    while (isdigit(ch)) {x=x*10+ch-0; ch=getchar();}
    return x*f;
}
struct data{
    struct sam{int l,fa,ch[30];} sam[maxn];
    int tot,root,last,head[30];
    void init(){
        clr(sam,0); tot=1; clr(head,0);
    }
    void extend(int c){
        int p,np,q,nq;
        p=last,np=++tot; last=np;
        sam[np].l=sam[p].l+1;
        for (;p&&!sam[p].ch[c];p=sam[p].fa) sam[p].ch[c]=np;
        if (!p) sam[np].fa=1;
        else {
            q=sam[p].ch[c];
            if (sam[p].l+1==sam[q].l) sam[np].fa=q;
            else {
                nq=++tot; sam[nq].l=sam[p].l+1;
                memcpy(sam[nq].ch,sam[q].ch,sizeof(sam[q].ch));
                sam[nq].fa=sam[q].fa;
                sam[np].fa=sam[q].fa=nq;
                for (;sam[p].ch[c]==q;p=sam[p].fa) sam[p].ch[c]=nq;
            }
        }
    }
    void sambuild(int n,char s[]){
        init();
        tot=last=1; 
        rep(i,1,n) extend(s[i]-a);
    }
    void quebuild(int n,char s[]){
        int o,p,c;
        init();
        rep(i,0,25) head[i]=1;
        rep(i,1,n){
            o=++tot; c=s[i]-a;
            rep(j,0,25) for (p=head[j];p&&!sam[p].ch[c];p=sam[p].fa) sam[p].ch[c]=o;    
            sam[o].fa=head[c];  head[c]=o;
        }
    }
} A,B;
struct node{int x,y;};
int solve(){
    queue<node> q; clr(f,0);
    q.push((node){1,1}); f[1][1]=0;
    while (!q.empty()){
        int ux=q.front().x,uy=q.front().y; q.pop();
        rep(i,0,25){
            if (!A.sam[ux].ch[i]) continue;
            if (!B.sam[uy].ch[i]) return f[ux][uy]+1;
            int vx=A.sam[ux].ch[i],vy=B.sam[uy].ch[i];
            if (!f[vx][vy]) q.push((node){vx,vy}),f[vx][vy]=f[ux][uy]+1;
        }
    }
    return -1;
}
int main(){
    scanf("%s",sa+1); scanf("%s",sb+1);
    n=strlen(sa+1); m=strlen(sb+1);
    A.sambuild(n,sa); B.sambuild(m,sb);
    printf("%d\n",solve());
    B.quebuild(m,sb);
    printf("%d\n",solve());
    A.quebuild(n,sa); B.sambuild(m,sb);
    printf("%d\n",solve());
    B.quebuild(m,sb);
    printf("%d\n",solve());
    return 0;
}
View Code

Luv Letter 【弱省胡策】Round #0

这道和上一道差不多的,上面是求字典序最小,这道是求出现次数,于是我们可以对AB都建自动机。然后从根开始拓扑一遍就可以得到每个节点后面有多少个子串,然后再记忆化搜索一遍枚举所有子串(子序列)统计答案就好了。

技术分享
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#define rep(i,l,r) for (int i=l;i<=r;i++)
#define down(i,l,r) for (int i=l;i>=r;i--)
#define clr(x,y) memset(x,y,sizeof(x))
#define maxn 4005
#define ll long long
#define inf int(1e9)
#define mm 1000000007
using namespace std;
char sa[maxn],sb[maxn];
ll f[maxn][maxn];
int vis[maxn][maxn];
int n,m;
int read(){
    int x=0,f=1; char ch=getchar();
    while (!isdigit(ch)) {if (ch==-) f=-1; ch=getchar();}
    while (isdigit(ch)) {x=x*10+ch-0; ch=getchar();}
    return x*f;
}
struct data{
    struct sam{int l,fa,ch[30];ll sz;} sam[maxn];
    int tot,root,last,head[30];
    void init(){
        clr(sam,0); tot=1; clr(head,0);
    }
    void extend(int c){
        int p,np,q,nq;
        p=last,np=++tot; last=np;
        sam[np].l=sam[p].l+1;
        for (;p&&!sam[p].ch[c];p=sam[p].fa) sam[p].ch[c]=np;
        if (!p) sam[np].fa=1;
        else {
            q=sam[p].ch[c];
            if (sam[p].l+1==sam[q].l) sam[np].fa=q;
            else {
                nq=++tot; sam[nq].l=sam[p].l+1;
                memcpy(sam[nq].ch,sam[q].ch,sizeof(sam[q].ch));
                sam[nq].fa=sam[q].fa;
                sam[np].fa=sam[q].fa=nq;
                for (;sam[p].ch[c]==q;p=sam[p].fa) sam[p].ch[c]=nq;
            }
        }
    }
    void dfs(int u){
        if (!u||sam[u].sz) return;
        sam[u].sz=1;
        rep(i,0,25) {
            int v=sam[u].ch[i];
            if (!v) continue;
            if (!sam[v].sz) dfs(v);
            sam[u].sz=(sam[u].sz+sam[v].sz)%mm;
        }
    }
    void sambuild(int n,char s[]){
        init();
        tot=last=1; 
        rep(i,1,n) extend(s[i]-a);
        dfs(1);
    }
    void quebuild(int n,char s[]){
        int o,p,c;
        init();
        rep(i,0,25) head[i]=1;
        rep(i,1,n){
            o=++tot; c=s[i]-a;
            rep(j,0,25) for (p=head[j];p&&!sam[p].ch[c];p=sam[p].fa) sam[p].ch[c]=o;    
              sam[o].fa=head[c];     head[c]=o; 
        }
        dfs(1);
    }
} A,B;
struct node{int x,y;};
ll dfs(int x,int y){
    if (vis[x][y]) return f[x][y];
    vis[x][y]=1;
    if (!y) return f[x][y]=A.sam[x].sz;
    f[x][y]=0;
    rep(i,0,25){
        int vx=A.sam[x].ch[i],vy=B.sam[y].ch[i];
        if (!vx) continue;
        f[x][y]=(f[x][y]+dfs(vx,vy))%mm;
    }
    return f[x][y]=f[x][y];
}
ll solve(){
    clr(vis,0); 
    return dfs(1,1);
}
int main(){
    scanf("%s",sa+1); scanf("%s",sb+1);
    n=strlen(sa+1); m=strlen(sb+1);
    A.sambuild(n,sa); B.sambuild(m,sb);
    printf("%lld\n",solve());
    B.quebuild(m,sb);
    printf("%lld\n",solve());
    A.quebuild(n,sa); B.sambuild(m,sb);
    printf("%lld\n",solve());
    B.quebuild(m,sb);
    printf("%lld\n",solve());
    return 0;
}
View Code

3998: [TJOI2015]弦论

字典序k大问题,对于多个相同子串算一个的那么按上面的方法扫一遍就行了,那多个相同子串算多个就要先算出每个节点的|right|,其实就是这个串的出现次数。(求right大小比较重要吧,蒟蒻理解了好久TAT。。。然后记住要倒序添加到fail上面。

技术分享
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define rep(i,l,r) for (int i=l;i<=r;i++)
#define down(i,l,r) for (int i=l;i>=r;i--)
#define clr(x,y) memset(x,y,sizeof(x))
#define maxn 1000500
using namespace std;
int last,tot,n,T,K;
char s[maxn];
int fa[maxn],ch[maxn][31],l[maxn],q[maxn],b[maxn],sum[maxn],val[maxn],vis[maxn];
int read(){
    int x=0,f=1; char ch=getchar();
    while (!isdigit(ch)) {if (ch==-) f=-1; ch=getchar();}
    while (isdigit(ch)) {x=x*10+ch-0; ch=getchar();}
    return x*f;
}
void expand(int c){
    int p,np,q,nq;
    p=last; last=np=++tot; l[np]=l[p]+1;
    for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
    if (!p) fa[np]=1;
    else {
        q=ch[p][c];
        if (l[q]==l[p]+1) fa[np]=q;
        else {
            nq=++tot; l[nq]=l[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[nq]));
            fa[nq]=fa[q];
            fa[np]=fa[q]=nq;
            for (;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
        }
    }  
    val[np]=1;
}
void dfs(int u,int k){
    if (k>=sum[u]) return;
    k-=val[u];
    if (!k) return;
    rep(j,0,25) if (ch[u][j]){
        int v=ch[u][j];
        if (sum[v]>=k){
            putchar(j+a);
            dfs(v,k);
            return ;
        }
        k-=sum[v];
    }
}
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    tot=last=1;
    rep(i,1,n) expand(s[i]-a);
    T=read(); K=read();
    rep(i,1,tot) b[l[i]]++;
    rep(i,1,n) b[i]+=b[i-1];
    rep(i,1,tot) q[b[l[i]]--]=i;
    down(i,tot,1) {
        int t=q[i];
        if (T==1) val[fa[t]]+=val[t];
        else val[t]=1;
    }
    val[1]=0;
    down(i,tot,1){
        int t=q[i];
        sum[t]=val[t];
        rep(j,0,25) sum[t]+=sum[ch[t][j]];
    }
    if (sum[1]<K) {puts("-1"); return 0;}
    dfs(1,K);
    return 0;
}
View Code

 

后缀自动机初探

标签:

原文地址:http://www.cnblogs.com/ctlchild/p/5141014.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!