zoukankan      html  css  js  c++  java
  • AC自动机

    时间有限,只过了板子和少数题目。。在应该搞DP的时候搞这个。。

    关于AC自动机的介绍,LuoGu日报讲的挺好的,可以比较好的入门。

    一、模板

    #include<bits/stdc++.h>
    #define RG register
    #define IL inline
    #define LL long long
    using namespace std;
    
    IL int gi () {
        RG int x=0,w=0; char ch=0;
        while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
        while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
        return w?-x:x;
    }
    
    const int N=1e6+10;
    
    int n,ans,tot[210];
    char str[N],ss[210][110];
    
    namespace AC_Automaton {
        queue <int> q;
        int num,cnt[N],f[N],tr[N][27];
    
        IL void New () {
            num=0;
            while (!q.empty()) q.pop();
            memset(f,0,sizeof(f));
            memset(tr,0,sizeof(tr));
            memset(cnt,0,sizeof(cnt));
        }
        
        IL void Insert (char s[],int id) {
            RG int i,p=0,now,len=strlen(s);
            for (i=0;i<len;++i) {
                now=s[i]-'a'+1;
                if (!tr[p][now]) tr[p][now]=++num;
                p=tr[p][now];
            }
            cnt[p]=id;
        }          
        
        IL void Get_Fail () {
            RG int i,p;
            for (i=1;i<=26;++i)
                if (tr[0][i]) q.push(tr[0][i]);
            while (!q.empty()) {
                p=q.front(),q.pop();
                for (i=1;i<=26;++i) 
                    if (tr[p][i]) f[tr[p][i]]=tr[f[p]][i],q.push(tr[p][i]);
                    else tr[p][i]=tr[f[p]][i];
                    // *类似并查集路径压缩*
                    // *所以可以一步到位*
            }
        }
    
        IL void Search (char s[]) {
            int i,j,p=0,len=strlen(s);
            for (i=0;i<len;++i) {
                p=tr[p][s[i]-'a'+1];
                for (j=p;j;j=f[j])
                    if (cnt[j]) ++tot[cnt[j]];
            }
        }
    }
    using namespace AC_Automaton;
    
    int main ()
    {
        RG int i;
        while ((n=gi())!=0) {
            ans=0,New();
            memset(tot,0,sizeof(tot));
            for (i=1;i<=n;++i)
                scanf("%s",ss[i]),Insert(ss[i],i);
            scanf("%s",str); Get_Fail();
            Search(str);
            for (i=1;i<=n;++i)
                if (tot[i]>ans) ans=tot[i];
            printf("%d
    ",ans);
            for (i=1;i<=n;++i)
                if (tot[i]==ans) printf("%s
    ",ss[i]);
        }
        return 0;
    }
    BY BHLLX

    我的不知道为什么跑的贼慢。。。

    二、LuoguP2444病毒

    硬是不会rand一个puts??

    对于一个刚入门AC自动机的人来说,这个题还是有点意思的。。

    先建一颗AC自动机,每个串结尾打上标记。

    我们这样考虑一下:

    如果我们拿最后那个合法的序列去AC自动机上匹配,那么我们会发现它恰好能避开所有打了标记的节点,通过fail指针在AC自动机上打转。

    那么我们是不是只要在这个AC自动机上找到一个不含任何病毒节点的环就好了??

    怎么找??dfs找就好了,先看代码。

    void dfs (int x) {
        if (pth[x]) {puts("TAK");exit(0);}
        if (vir[x]||vis[x]) return;//vir是病毒标记
        pth[x]=vis[x]=1;
        dfs(tr[x][0]),dfs(tr[x][1]);
        pth[x]=0;
    }

    开两个数组分别代表 当前路径已经过、历史曾访问过,用于找环。

    另:我们发现好像是一直沿着trie树往下走,实则不然,因为我们构造fail时,已经把 每个节点没有的那个子节点 和 它父亲的fail指向的节点 的这个节点相连,即类似并查集

    的路径压缩操作的那一步,所以其实它是在这颗trie树上跳来跳去的。。。(根据样例画图理解最佳)

    #include<bits/stdc++.h>
    #define RG register
    #define IL inline
    #define LL long long
    using namespace std;
    
    IL int gi () {
        RG int x=0,w=0; char ch=0;
        while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
        while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
        return w?-x:x;
    }
    
    const int N=3e4+10;
    
    queue <int> q;
    char str[N];
    int n,num,f[N],vir[N],tr[N][2],pth[N],vis[N];
    
    IL void Insert (char *s) {
        RG int i,p=0,now,len=strlen(s);
        for (i=0;i<len;++i) {
            now=str[i]^48;
            if (!tr[p][now]) tr[p][now]=++num;
            p=tr[p][now];
        }
        vir[p]=1;
    }
    
    IL void Get_Fail () {
        RG int i,x,now;
        if (tr[0][0]) q.push(tr[0][0]);
        if (tr[0][1]) q.push(tr[0][1]);
        while (!q.empty()) {
            x=q.front(),q.pop();
            for (i=0;i<2;++i) {
                now=tr[x][i];
                if (!now) tr[x][i]=tr[f[x]][i];
                else f[now]=tr[f[x]][i],vir[now]|=vir[f[now]],q.push(now);    
            }
        }
    }
    
    void dfs (int x) {
        if (pth[x]) {puts("TAK");exit(0);}
        if (vir[x]||vis[x]) return;
        pth[x]=vis[x]=1;
        dfs(tr[x][0]),dfs(tr[x][1]);
        pth[x]=0;
    }
    
    int main ()
    {
        RG int i;
        n=gi();
        for (i=1;i<=n;++i)
            scanf("%s",str),Insert(str);
        Get_Fail(),dfs(0);
        puts("NIE");   
        return 0;
    }
    BY BHLLX

    三、luoguP2414阿狸的打字机

    刚了差不多一天。。。题是真的一道好题。。。

    详细介绍可以看yyb神仙写的博客,写的很好。

    这种题先打一个暴力,70分,还挺好想。。(然而我还是看了yyb神仙的代码调了一波)

    #include<bits/stdc++.h>
    #define RG register
    #define IL inline
    #define LL long long
    using namespace std;
    
    IL int gi () {
        int x=0; char ch=0;
        while (ch<'0'||ch>'9') ch=getchar();
        while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
        return x;
    }
    
    const int N=1e5+10;
    
    queue <int> q;
    char str[N],ss[N];
    int n,num,wd[N],f[N],tr[N][27],fa[N],End[N],cnt[N],match[N],ans[N];
    
    struct Query {int id,x,y;}qur[N];
    IL bool cmp (Query A,Query B) {return A.y<B.y;}
    
    IL void Insert (char *s,int len,int id) {
        RG int i,p=0,now;
        for (i=0;i<len;++i) {
            now=s[i]-'a'+1;
            if (!tr[p][now]) tr[p][now]=++num;
            fa[tr[p][now]]=p,p=tr[p][now];
        }
        End[p]=1,wd[id]=p;
    }
    
    IL void Get_Fail () {
        RG int i,x,now;
        for (i=1;i<=26;++i)
            if (tr[0][i]) q.push(tr[0][i]);
        while (!q.empty()) {
            x=q.front(),q.pop();
            for (i=1;i<=26;++i) {
                now=tr[x][i];
                if (!now) tr[x][i]=tr[f[x]][i];
                else f[now]=tr[f[x]][i],q.push(now);
            }
        }
    }
    
    IL void Search (int id) {
        RG int i,now=wd[id];
        while (now) {
            for (i=now;i;i=f[i])
                if (End[i]) ++cnt[i];
            now=fa[now];
        }
    }
    
    int main ()
    {
        RG int i,j,k,x,y,len,id;
        scanf("%s",str),n=gi(),len=strlen(str);
        for (i=1;i<=n;++i) x=gi(),y=gi(),qur[i]=(Query){i,x,y};
        sort(qur+1,qur+n+1,cmp);
        for (i=0,j=0,id=0;i<len;++i) {
            if (str[i]=='P') Insert(ss,j,++id);
            else if (str[i]=='B') --j;
            else ss[j++]=str[i];
        }
        Get_Fail();
        for (i=1;i<=n;++i) {
            if (!match[qur[i].y]) {
                match[qur[i].y]=1;
                memset(cnt,0,sizeof(cnt));
                Search(qur[i].y);
            }
            ans[qur[i].id]=cnt[wd[qur[i].x]];
        }
        for (i=1;i<=n;++i) printf("%d
    ",ans[i]);
        return 0;
    }
    BY BHLLX

    上述代码有一个很耗时的问题,就是每次都重新插入,其实可以记录一下每个节点的父亲,就可以很快的继续执行插入。不然读入都会超时。

    然后我们怎么考虑这个问题:

    我们发现是求一个串x在另一个串y中出现了多少次。

    反过来考虑的话就是分别求出:对于每一个y它能给对应的x的答案产生多少贡献

    这个怎么求呢?我们要利用到fail(这个东西真的很神仙)。

    一个节点对应着一个fail,倒过来的话那么实际上这就是一棵树,即fail树。

    这样再来看我们刚刚的问题,y对x有贡献,那么在fail树上,y或 原rie树上y的某个祖先(即这个单词的每一个字母) 肯定在x的某棵子树(fail树)上。

    那么我们只要在原trie树上DFS,依次标记每一个字母赋值1,到一个y的结尾,对于每一个要求的x,就相当于在fail树上求x的子树和。

    实现的话,可以在fail树dfs序用树状数组做就好了。。

    #include<bits/stdc++.h>
    #define RG register
    #define IL inline
    #define LL long long
    #define lowbit(x) x&(-x)
    using namespace std;
    
    IL int gi () {
        int x=0; char ch=0;
        while (ch<'0'||ch>'9') ch=getchar();
        while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
        return x;
    }
    
    const int N=2e5+10;
    
    queue <int> q;
    char str[N],ss[N];
    int tot,head[N];
    int Time,low[N],dfn[N],ql[N],qr[N],BIT[N];
    int n,num,wd[N],fa[N],f[N],tr[N][27],Tr[N][27],End[N],ans[N];
    
    struct Query {int id,x,y;}qur[N];
    IL bool cmp (Query A,Query B) {return A.y<B.y;}
    
    struct Edge {int next,to;}e[N];
    IL void make (int from,int to) {e[++tot]=(Edge){head[from],to};head[from]=tot;}
    IL void Get_tree () {for (RG int i=1;i<=num;++i) make(f[i],i);}
    
    IL void modify (int x,int v) {for(;x<=Time;x+=lowbit(x)) BIT[x]+=v;}
    IL int query (int x) {RG int ans=0;for(;x;x-=lowbit(x)) ans+=BIT[x];return ans;}
    
    IL void Get_Fail () {
        RG int i,x,now;
        for (i=1;i<=26;++i)
            if (tr[0][i]) q.push(tr[0][i]);
        while (!q.empty()) {
            x=q.front(),q.pop();
            for (i=1;i<=26;++i) {
                now=tr[x][i];
                if (!now) tr[x][i]=tr[f[x]][i];
                else f[now]=tr[f[x]][i],q.push(now);
            }
        }
    }
    
    void dfs (int x) {
        RG int i;
        dfn[x]=++Time;
        for (i=head[x];i;i=e[i].next) dfs(e[i].to);
        low[x]=Time;
    }
    
    void DFS (int x) {
        RG int i;
        modify(dfn[x],1);
        if (End[x])
            for (i=ql[End[x]];i<=qr[End[x]];++i) 
                ans[qur[i].id]=query(low[wd[qur[i].x]])-query(dfn[wd[qur[i].x]]-1);
        for (i=1;i<=26;++i)
            if (Tr[x][i]) DFS(Tr[x][i]);
        modify(dfn[x],-1);
    }
    
    int main ()
    {
        freopen ("dat.in","r",stdin);
        freopen ("dat.out","w",stdout);
        RG int i,j,p=0,x,y,len,id,now;
        scanf("%s",str),n=gi(),len=strlen(str);
        for (i=1;i<=n;++i) x=gi(),y=gi(),qur[i]=(Query){i,x,y};
        sort(qur+1,qur+n+1,cmp);
        for (i=1,j=1;i<=n;++i) {
            ql[qur[i].y]=i;
            while (qur[j].y==qur[i].y) ++j;
            qr[qur[i].y]=j-1,i=j-1;
        }
        for (i=0,j=0,id=0;i<len;++i) {
            if (str[i]=='P') End[p]=++id,wd[id]=p;
            else if (str[i]=='B') p=fa[p];
            else {
                now=str[i]-'a'+1;
                if (!tr[p][now]) tr[p][now]=++num,fa[num]=p;
                p=tr[p][now];
            }
        }
        for (i=0;i<=num;++i)
            for (j=1;j<=26;++j) Tr[i][j]=tr[i][j];
        Get_Fail(),Get_tree(),dfs(0),DFS(0);
        for (i=1;i<=n;++i) printf("%d
    ",ans[i]);
        return 0;
    }
    BY BHLLX

    四、LuoguP3966单词

    和上一题基本一样,只是要注意可能会有重复单词。。

    #include<bits/stdc++.h>
    #define RG register
    #define IL inline
    #define LL long long
    #define lowbit(x) x&(-x)
    using namespace std;
    
    const int N=1e6+10;
    
    queue <int> q;
    char s[N];
    int n,ans[N],Ed[N],wd[N],BIT[N];
    int Time,dfn[N],low[N];
    int tot,head[N],To[N],next[N];
    int num,tr[N][27],Tr[N][27],F[N];
    
    IL void make(int fr,int to) {next[++tot]=head[fr],To[tot]=to,head[fr]=tot;}
    IL void Get_Edge() {for(RG int i=1;i<=num;++i) make(F[i],i);}
    
    IL void modify(int x,int v) {for(;x<=Time;x+=lowbit(x)) BIT[x]+=v;}
    IL int query(int x) {RG int sum=0;for(;x;x-=lowbit(x)) sum+=BIT[x];return sum;}
    
    IL void Insert(char *s,int id) {
        int i,p=0,now,len=strlen(s);
        for (i=0;i<len;++i) {
            now=s[i]-'a'+1;
            if (!tr[p][now]) tr[p][now]=++num;
            p=tr[p][now];
        }
        ++Ed[p],wd[id]=p;
    }
    
    IL void Get_Fail() {
        RG int i,x,now;
        for (i=1;i<=26;++i)
            if (tr[0][i]) q.push(tr[0][i]);
        while (!q.empty()) {
            x=q.front(),q.pop();
            for (i=1;i<=26;++i) {
                now=tr[x][i];
                if (!now) tr[x][i]=tr[F[x]][i];
                else F[tr[x][i]]=tr[F[x]][i],q.push(now);
            }
        }
    }
    
    void dfs (int x) {
        dfn[x]=++Time;
        for (RG int i=head[x];i;i=next[i]) dfs(To[i]);
        low[x]=++Time;
    }
    
    void DFS (int x) {
        RG int i;
        modify(dfn[x],1);
        if (Ed[x]) {
            for (i=1;i<=n;++i)
                ans[i]+=(query(low[wd[i]])-query(dfn[wd[i]]-1))*Ed[x];
        }
        for (i=1;i<=26;++i)
            if (Tr[x][i]) DFS(Tr[x][i]);
        modify(dfn[x],-1);
    }
     
    int main ()
    {
        RG int i,j;
        scanf("%d",&n);
        for (i=1;i<=n;++i)
            scanf("%s",s),Insert(s,i);
        for (i=0;i<=num;++i)
            for (j=1;j<=26;++j) Tr[i][j]=tr[i][j];
        Get_Fail(),Get_Edge(),dfs(0),DFS(0);
        for (i=1;i<=n;++i) printf("%d
    ",ans[i]);
        return 0;
    }
    BY BHLLX

     五、LuoguP3966最短母串问题

    其实这一是一道很好的DP题,这里用某神犇的AC自动机+最短路解决。

    我们先对所有的串建一个AC自动机。

    若一个串x的后缀 和另一个串y的前缀 可以重合一部分,那么在Trie图上x可以直接连向y的重合部分以下的那个节点。

    所以我们直接在这个Trie图上跑一个 从根节点出发的 经过所有单词末尾节点的 最短路,用SPFA,因为可能会有环。

    具体实现的话,我们相当与就在原来的基础上多了一维状态S,S是表示经过的末尾节点的集合。

    至于方案的输出,我们直接DFS,详见代码。

    注意:

    ① 空间只有32MB,还是得注意一下。

    ② 再跑最短路之前,我们需要提前把一个节点i所有的Fail祖先的结尾状态继承一下,否则有可能会造成漏掉那些本身就是其他串的子串的串。

     例如:HNOI,NOI,NOIP,IOI。 Ans:HNOIPIOI。可以用来检验一下。

    #include<bits/stdc++.h>
    #define RG register
    #define IL inline
    #define Mp make_pair
    using namespace std;
    
    IL int gi () {
        RG int x=0,w=0; char ch=0;
        while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
        while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
        return w?-x:x;
    }
    
    const int N=13;
    const int M=605;
    const int INF=0x3f3f3f3f;
    
    queue <int> q;
    queue < pair<int,int> > que;
    string ss;
    char str[N][51];
    int num,tr[M][27],F[M];
    int n,All,ans=INF,Inq[M][1<<N],Ed[M],f[M][1<<N];
    
    IL void Insert(char *s,int id) {
        int i,p=0,len=strlen(s),now;
        for (i=0;i<len;++i) {
            now=s[i]-'A'+1;
            if (!tr[p][now]) tr[p][now]=++num;
            p=tr[p][now];
        }
        Ed[p]|=(1<<id-1);
    }
    
    IL void Get_Fail() {
        RG int i,x,now;
        for (i=1;i<=26;++i)
            if (tr[0][i]) q.push(tr[0][i]);
        while (!q.empty()) {
            x=q.front(),q.pop();
            for (i=1;i<=26;++i) {
                now=tr[x][i];
                if (!now) tr[x][i]=tr[F[x]][i];
                else F[now]=tr[F[x]][i],q.push(now);
            }
        }
    }
    
    IL void SPFA() {
        RG int i,j,x,s,y,now;
        for (i=1;i<=num;++i)
            for (j=i;j;j=F[j]) Ed[i]|=Ed[j];
        memset(f,0x3f,sizeof(f));
        f[0][0]=0;
        Inq[0][0]=1,que.push(Mp(0,0));
        while (!que.empty()) {
            x=que.front().first,s=que.front().second,que.pop(),Inq[x][s]=0;
            for (i=1;i<=26;++i) {
                if(!(y=tr[x][i])) continue;
                now=s|Ed[y];
                if (f[x][s]+1<f[y][now]) {
                    f[y][now]=f[x][s]+1;
                    if (!Inq[y][now]) que.push(Mp(y,now)),Inq[y][now]=1;
                }
            }
        }
        for (i=1;i<=num;++i) ans=min(ans,f[i][All]);
    }
    
    void DFS(int x,int state,string s)  {
        if (f[x][state]>ans) return;
        if (state==All&&f[x][state]==ans) {cout<<s<<endl;exit(0);}
        RG int i,y;
        for (i=1;i<=26;++i) 
            if ((y=tr[x][i])) {
                if (f[x][state]+1==f[y][state|Ed[y]]) DFS(y,state|Ed[y],s+(char)(i+'A'-1));
            }
    }
    
    int main ()
    {
        RG int i;
        n=gi(),All=(1<<n)-1;
        for (i=1;i<=n;++i)
            scanf("%s",str[i]),Insert(str[i],i);    
        Get_Fail(),SPFA(),DFS(0,0,ss);    
        return 0;
    }
    BY BHLLX
  • 相关阅读:
    UVA 1025 A Spy in the Metro DP水题
    ZOJ 3814 Sawtooth Puzzle BFS
    ZOJ 3816 Generalized Palindromic Number
    UVA 10859 Placing Lampposts 树形DP
    UVA 11825 Hackers' Crackdown 状压DP
    POJ 2887 Big String 线段树 离线处理
    POJ 1635 Subway tree systems Hash法判断有根树是否同构
    BZOJ 3110 k大数查询 & 树套树
    sdoi 2009 & 状态压缩
    来自于2016.2.24的flag
  • 原文地址:https://www.cnblogs.com/Bhllx/p/10329982.html
Copyright © 2011-2022 走看看