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
  • 相关阅读:
    加载页面(Loading)
    js判断手机连接网络类型
    combotree的加载方法
    jquery.util.easyui.dialog
    datagrid后台分页js.js
    安卓使用Canvas绘制工作日程表
    【Oracle】OGG数据初始化之RMAN
    怎样优雅的管理ActionBar
    openwrt undefined reference to ‘getpwent_r’
    通过文件对照工具Merge数据库
  • 原文地址:https://www.cnblogs.com/Bhllx/p/10329982.html
Copyright © 2011-2022 走看看