后缀自动机初探

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

最近重新填坑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;
}

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;
}

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;
}

 3238: [Ahoi2013]差异

逆序建出sam,它的fail树就是后缀树,然后dfs扫一遍就好了。

#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 1005000
#define ll long long
#define inf int(1e9)
#define mm 1000000007
using namespace std;
char s[maxn];
ll ans;
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;
    void init(){
        clr(sam,0); tot=1;
    }
    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;
            }
        }
        sam[np].sz=1;
    }
    void sambuild(int n,char s[]){
        tot=last=1;
        down(i,n,1) extend(s[i]-‘a‘);
    }
} A,B;
ll sum[maxn];
int l[maxn],head[maxn],tot;
struct node{int obj,pre;
}e[maxn];
void insert(int x,int y){
    e[++tot].obj=y; e[tot].pre=head[x]; head[x]=tot;
}
void dfs(int u){
    for (int j=head[u];j;j=e[j].pre){
        int v=e[j].obj;
        dfs(v);
        ans-=2LL*l[u]*sum[u]*sum[v];
        sum[u]+=sum[v];
    }
}
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    ans=1LL*n*(n-1)*(n+1)/2;
    A.sambuild(n,s);
    rep(i,1,A.tot) l[i]=A.sam[i].l,sum[i]=A.sam[i].sz,insert(A.sam[i].fa,i);
    dfs(1);
    printf("%lld\n",ans);
    return 0;
}

广义后缀自动机

(其实就是在trie上建一棵后缀自动机辣。有多个串的时候记住每次都要让last=1,否则串与串会连在一起形成新的串。

如果当前要加入的点已经有了且l[q]=l[p]+1,那么np=q,否则就新增一个点。。如果当前加入的点并没有,那就跟以前维护SAM一样就好了。。(我们可以花大量常数开map偷懒)

    void expand(int x,int y){
        int p,q,np,nq;
        p=last;
        if ((q=go[p][x])) {
            if (l[q]==l[p]+1) last=q;
            else {
                nq=++tot; l[nq]=l[p]+1;
                go[nq]=go[q];
                fa[nq]=fa[q]; fa[q]=nq;
                for (;p&&go[p][x]==q;p=fa[p]) go[p][x]=nq;
                last=nq;
            }
        }
        else {
            np=++tot; l[np]=l[p]+1;
            for (;p&&!go[p][x];p=fa[p]) go[p][x]=np;
            if (!p) fa[np]=1;
            else {
                q=go[p][x];
                if (l[q]==l[p]+1) fa[np]=q;
                else {
                    nq=++tot; l[nq]=l[p]+1;
                    go[nq]=go[q];
                    fa[nq]=fa[q]; fa[q]=fa[np]=nq;
                    for (;p&&go[p][x]==q;p=fa[p]) go[p][x]=nq;
                }
            }
            last=np;
        }
    }

BZOJ2780: [Spoj]8093 Sevenk Love Oimaster

(题面简直笑抽2333333

题意是给你n个串,m个询问串,每个询问串是n个串中多少个串的字串。。

首先我们把广义后缀自动机建出来,然后fail[i]->i,建立一棵树,可以得到SAM每个节点的dfs序。

对于询问我们在trie上跑得到末尾的那个点,那么它子树的信息就是答案了。

问题转化为一个区间上有多少个不同的数字。上个树状数组。

#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#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 600500
#define ll long long
#define inf int(1e9)
#define mm 1000000007
#define low(x) x&(-x)
using namespace std;
char s[maxn];
int n,m;
int ans[maxn],dfn[maxn][2],pos[maxn],t[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;
}
struct edge{
    struct data{int obj,pre;}e[maxn];
    int head[maxn],tot;
    void insert(int x,int y){
        e[++tot].obj=y; e[tot].pre=head[x]; head[x]=tot;
    }
}A,B;
struct node{int x,y,id;
}a[maxn];
struct SAM{
    int tot,last,idx;
    int l[maxn],fa[maxn];
    map<int,int> go[maxn];
    void expand(int x,int y){
        int p,q,np,nq;
        p=last;
        if ((q=go[p][x])) {
            if (l[q]==l[p]+1) last=q;
            else {
                nq=++tot; l[nq]=l[p]+1;
                go[nq]=go[q];
                fa[nq]=fa[q]; fa[q]=nq;
                for (;p&&go[p][x]==q;p=fa[p]) go[p][x]=nq;
                last=nq;
            }
        }
        else {
            np=++tot; l[np]=l[p]+1;
            for (;p&&!go[p][x];p=fa[p]) go[p][x]=np;
            if (!p) fa[np]=1;
            else {
                q=go[p][x];
                if (l[q]==l[p]+1) fa[np]=q;
                else {
                    nq=++tot; l[nq]=l[p]+1;
                    go[nq]=go[q];
                    fa[nq]=fa[q]; fa[q]=fa[np]=nq;
                    for (;p&&go[p][x]==q;p=fa[p]) go[p][x]=nq;
                }
            }
            last=np;
        }
        B.insert(last,y);
    }
    void dfs(int u){
        dfn[u][0]=++idx; pos[idx]=u;
        for (int j=A.head[u];j;j=A.e[j].pre){
            int v=A.e[j].obj;
            if (!dfn[v][0]) dfs(v);
        }
        dfn[u][1]=idx;
    }
    void init(int len,int y){
        last=1;
        rep(i,1,len) expand(s[i],y);
    }
    node find(int x){
        scanf("%s",s+1); int len=strlen(s+1),now=1;
        rep(i,1,len) now=go[now][s[i]];
        if (!now) return (node){2,1,x};
        else return (node){dfn[now][0],dfn[now][1],x};
    }
}T;
bool cmp(node a,node b){
    return a.y<b.y;
}
int vis[maxn];
void add(int x,int y){
    while (x<=T.tot) {
        t[x]+=y; x+=low(x);
    }
}
int ask(int x){
    int ans=0;
    while (x){
        ans+=t[x]; x-=low(x);
    }
    return ans;
}
int main(){
      n=read(); m=read();
      T.tot=1; T.idx=0;
      rep(i,1,n) {
          scanf("%s",s+1); T.init(strlen(s+1),i);
      }
      rep(i,1,T.tot) A.insert(T.fa[i],i);
      T.dfs(1);
      rep(i,1,m) a[i]=T.find(i);
      sort(a+1,a+1+m,cmp);
      int now=1;
      rep(i,1,T.tot){
          for (int j=B.head[pos[i]];j;j=B.e[j].pre){
              int v=B.e[j].obj;
              if (vis[v]) add(vis[v],-1);
              vis[v]=i;
              add(vis[v],1);
          }
          while (a[now].y==i) ans[a[now].id]=ask(a[now].y)-ask(a[now].x-1),now++;
      }
      rep(i,1,m) printf("%d\n",ans[i]);
    return 0;
}

时间: 2024-08-06 20:08:29

后缀自动机初探的相关文章

hiho一下第128周 后缀自动机二&#183;重复旋律5

#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中出现了多少不同的旋律? 解题方法提示 输入 共一行,包含一个由小写字母构成的字符串.字符串长度不超过 1000000. 输出 一行一个整数,表示答案. 样例输入 aab 样例输出 5 解题方法提示 小Hi:本周的题目其实就是给定一个字符串S,要求出S的所有不同子串的数

后缀自动机总结

后缀自动机是一种确定性有限自动机(DFA),它可以且仅可以匹配一个给定串的任意后缀. 构造一个可以接受一个给定串的所有后缀的不确定性有限自动机(NFA)是很容易的,我们发现我们用通用的将NFA转换成对应DFA的算法转换出来的DFA的状态数都很小(O(n)级别的,远远达不到指数级别).于是,人们就开始研究这种特殊的NFA,并提出了在线增量算法,用O(n)的时间复杂度构造该NFA的DFA.在转换过程中,DFA中对应的NFA中的状态集合其实就是我们的right集合.——————以上在胡扯———————

BZOJ 2946 Poi2000 公共串 后缀自动机

题目大意:求n个串的最长公共子串 太久没写SAM了真是-- 将第一个串建成后缀自动机,用其它的串进去匹配 每个节点记录每个串在上面匹配的最大长度 那么这个节点对答案的贡献就是所有最大长度的最小值 对所有贡献取最大就行了= = 这最大最小看着真是别扭 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define M 10100 using namesp

如何优雅的研究 RGSS3 番外(一) ruby 实现的后缀自动机

*我真的不会 ruby 呀* #encoding:utf-8 #============================================================================== # ■ Suffix_Automaton #------------------------------------------------------------------------------ # 后缀自动机. #============================

【BZOJ3926】[Zjoi2015]诸神眷顾的幻想乡 广义后缀自动机

[BZOJ3926][Zjoi2015]诸神眷顾的幻想乡 Description 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日. 粉丝们非常热情,自发组织表演了一系列节目给幽香看.幽香当然也非常高兴啦. 这时幽香发现了一件非常有趣的事情,太阳花田有n块空地.在过去,幽香为了方便,在这n块空地之间修建了n-1条边将它们连通起来.也就是说,这n块空地形成了一个树的结构. 有n个粉丝们来到了太阳花田上.为了表达对幽香生日的祝

【后缀自动机】【拓扑排序】【动态规划】hihocoder1457 后缀自动机四&#183;重复旋律7

解题方法提示 小Hi:我们已经学习了后缀自动机,今天我们再来看这道有意思的题. 小Ho:好!这道题目让我们求的是若干的数字串所有不同子串的和. 小Hi:你能不能结合后缀自动机的性质来思考如何解决本题? 小Ho:这道题目既然是关于子串,那么我知道从后缀自动机的所有状态中包含的子串的集合恰好对应原串的所有不重复子串. 小Hi:很好.那你可以先简化问题,想想只有一个串怎么做? 小Ho:好的.这个难不倒我.我上次已经知道如何计算一个串所有不同子串的数量,现在这题也类似,只不过计算更加复杂一点. 小Hi:

BZOJ3926 ZJOI2015 诸神眷顾的幻想乡 后缀自动机+DFS

题意:给定一颗字符树,求树中路径所构成的不同的字符串的数量,其中AB和BA视作不同的字符串 题解: 题目里有这样一句话:太阳花田的结构比较特殊,只与一个空地相邻的空地数量不超过20个. 一共有10W个点,却只有20个叶子……因此树上所有的字串就是以叶子为起点搜索出的所有字串,丽洁姐真的好善良啊- -(无雾) 这样从每个点开始就能跑出来一颗Trie树,对Trie构造广义后缀自动机——每个节点看成是一个根,在后面加字符的时候和普通的SAM一样. 然后在SAM上用DFS统计不同字串的数量即可 #inc

SPOJ 1812 Longest Common Substring II(后缀自动机)

[题目链接] http://www.spoj.com/problems/LCS2/ [题目大意] 求n个串的最长公共子串 [题解] 对一个串建立后缀自动机,剩余的串在上面跑,保存匹配每个状态的最小值, 取最小值中的最大值即可.由于跑的地方只记录了匹配结尾的状态, 所以还需要更新parent树上的状态,既然匹配到了子节点, 那么parent树链上的值就都能够取到l, 一开始给每个不同状态按照l从小到大分配储存地址, 这样,我们就可以从匹配长度最长的开始更新parent树的情况. [代码] #inc

hdu5853 (后缀自动机)

Problem Jong Hyok and String 题目大意 给你n个字符串,有q个询问. 定义set(s)={(i,j)} 表示 s在第i个字符串中出现,且末尾位置为j. 对于一个询问,求set(Qi)=set(t) ,t的数量. (n,q<=10^5 , 字符串总长<=10^5) 解题分析 直接将n个串塞进一个后缀自动机里面. 对于一个询问串qi,找到其在后缀自动机中出现的位置j. 则答案为len[j] - len[fail[j]] . (具体为什么还需要好好斟酌一下) 参考程序 1