zoukankan      html  css  js  c++  java
  • 洛谷P3796 【模板】AC自动机(加强版)(AC自动机)

    洛谷题目传送门
    先膜一发yyb巨佬 orz
    想学ac自动机的话,推荐一下yyb巨佬的博客,本蒟蒻也是从那里开始学的。

    思路分析

    裸的AC自动机,这里就不讲了。主要是这题太卡时了,尽管时限放的很大了。。。。。。
    用传统方法匹配时,每走到一个新位置,都是要统计答案的。怎么统计呢?暴力跳(fail),把沿路上能够产生答案的算上,直到跳到根才停下来。这里的时间复杂度是 (O(70N)),在有多组数据的情况下其实是很吃亏的。(蒟蒻用了(fread),根据目测,最大的一个点输入大小在(30MB)以上!!!)或许这里就是TLE的恶魔。。。。。。
    我们发现,在暴跳的过程中,走到的很多节点,对答案是没有贡献的。很自然的想到,这些点可不可以直接跳过呢?或者说,一段没有贡献的路径,我们可不可以把它像并查集一样搞一个路径压缩呢?
    因为每个点有且仅有惟一的(fail),所以这样做是可行的!我们设一个状态(g[i]),表示沿着(i)向上跳(fail)跳到的第一个能对答案产生贡献的位置(也就是某个单词结尾的(end)位置),找不到的话当然设为虚根(0)了。此状态可以通过递推得到,在求(fail)的时候也一起推出来了。
    有了这个想法,具体推法也就很明显了。如果(fail[i])为某单词结尾,那么(g[i]=fail[i])。否则(g[i]=g[fail[i]])
    统计答案就不跳(fail)了,而是跳(g),同样是跳到根为止。
    这种优化实际复杂度并没有变(最坏情况下是一样的),但是实际效果还是挺好的,比较大的点可以优化(25\%)以上。
    实在TLE的话, (fread,fwrite,register,inline,)O2套餐奉上。。。。。。
    我不会告诉大家yyb_test 896ms成功冲到了rank1。。。。。。(对你没看错,是yyb_test而不是FlashHu)
    附上数组版代码,可能有点丑

    #include<cstdio>
    #include<cstring>
    #define now c[u][*p-'a']
    #define skip while(*++p<=' ')//跳过空字符
    const int N=1000009;
    char s[N<<6],o[N<<4],*m[159];//m存每个模式串的起始位置指针
    int c[N][26],f[N],e[N],g[N],q[N],a[159];//f即fail,e即end,q队列,g如上描述
    int main()
    {
        fread(s,1,sizeof(s),stdin);//奇技淫巧之fread
        register char *p=s,*p1=o;//p读入,p1输出
        register int n,cnt,i,h,t,u,v,mx;
        while((n=*p&15))
        {
            while(*++p>='0')
                n*=10,n+=*p&15;
            cnt=h=t=0;
            //建自动机开始
            for(i=1;i<=n;++i)
            {
                skip;m[i]=p;
                for(u=0;*p>='a';++p)
                    u=now?now:(now=++cnt);
                e[u]=i;//end存的是模式串编号而不是个数了
            }
            skip;m[i]=p;
            //bfs开始,求fail以及g
            for(i=0;i<26;++i)//第一层提前处理
                if(c[0][i])q[++t]=c[0][i];
            while(h<t)
            {
                u=q[++h];
                for(i=0;i<26;++i)
                    if((v=c[u][i]))
                    {
                        f[q[++t]=v]=c[f[u]][i];
                        g[v]=e[f[v]]?f[v]:g[f[v]];
                    }
                    else c[u][i]=c[f[u]][i];//把空儿子置为fail的对应儿子,匹配的时候方便点
            }
            //匹配开始
            for(u=0;*p>='a';++p)
                for(v=u=now;v;v=g[v])//沿着g统计答案
                    ++a[e[v]];
            //统计答案开始,其实不用sort,扫一遍就好啦
            mx=t=0;
            for(i=1;i<=n;++i)
                if(mx<a[i])mx=a[q[t=1]=i];
                else if(mx==a[i])q[++t]=i;
            //输出答案开始
            sprintf(p1,"%d
    ",mx);
            while(*++p1);
            for(i=1;i<=t;++i)
            {
                memcpy(p1,m[q[i]],m[q[i]+1]-m[q[i]]);//我也不知道为什么这里用strcpy会MLE,难道产生了缓存空间?!
                p1+=m[q[i]+1]-m[q[i]];
            }//记得多组数据,弄完一组全清空
            memset(c,0,++cnt*104);
            memset(f,0,cnt<<2);
            memset(e,0,cnt<<2);
            memset(g,0,cnt<<2);
            memset(a,0,(n+1)<<2);
            skip;
        }
        fwrite(o,1,p1-o,stdout);//奇技淫巧之fwrite
        return 0;
    }
    

    update:

    突然想到(fail)的形态是一棵树,那么(g)显然也是,那么匹配的每个点不就是对在(g)树上的一条链上的所有点产生(1)的贡献吗?何必还要暴力跳,直接把贡献暂时存起来最后再做一遍树形DP不就行了么。。。。。。

    复杂度成功降至线性(O(sum|S|+sum|T|))

    #include<cstdio>
    #include<cstring>
    #define R register
    #define now c[u][*p-'a']
    #define skip while(*++p<=' ')//跳过空字符
    const int N=159,S=20009,T=1000009;
    char s[T<<6],o[T],*m[N];//m存每个模式串的起始位置指针
    int c[S][26],f[S],e[S],g[S],q[S],a[N],he[N],ne[N],to[N];
    //f即fail,e即end,q队列,g如上描述
    void dp(R int x){
        for(R int i=he[x];i;i=ne[i])
            dp(to[i]),a[x]+=a[to[i]];
    }
    int main()
    {
        fread(s,1,sizeof(s),stdin);//奇技淫巧之fread
        R char *p=s,*p1=o;//p读入,p1输出
        R int n,cnt,i,h,t,pe,u,v,mx;
        while((n=*p&15))
        {
            while(*++p>='0')
                n*=10,n+=*p&15;
            cnt=h=t=pe=0;
            //建自动机开始
            for(i=1;i<=n;++i)
            {
                skip;m[i]=p;
                for(u=0;*p>='a';++p)
                    u=now?now:(now=++cnt);
                at[e[u]=i]=u;//end存的是模式串编号而不是个数了
            }
            skip;m[i]=p;
            //bfs开始,求fail以及g
            for(i=0;i<26;++i)//第一层提前处理
                if(c[0][i])q[++t]=c[0][i];
            while(h<t)
            {
                if(e[u=q[++h]])
                    to[++pe]=e[u],ne[pe]=he[e[g[u]]],he[e[g[u]]]=pe;
                for(i=0;i<26;++i)
                    if((v=c[u][i]))
                    {
                        f[q[++t]=v]=c[f[u]][i];
                        g[v]=e[f[v]]?f[v]:g[f[v]];
                    }
                    else c[u][i]=c[f[u]][i];//把空儿子置为fail的对应儿子,匹配的时候方便点
            }
            //匹配开始
            for(u=0;*p>='a';++p)
                u=now,++a[e[u]?e[u]:e[g[u]]];
            //统计答案开始,其实不用sort,扫一遍就好啦
            dp(0);
            mx=t=0;
            for(i=1;i<=n;++i)
                if(mx<a[i])mx=a[q[t=1]=i];
                else if(mx==a[i])q[++t]=i;
            //输出答案开始
            sprintf(p1,"%d
    ",mx);
            while(*++p1);
            for(i=1;i<=t;++i)
            {
                memcpy(p1,m[q[i]],m[q[i]+1]-m[q[i]]);//我也不知道为什么这里用strcpy会MLE,难道产生了缓存空间?!
                p1+=m[q[i]+1]-m[q[i]];
            }//记得多组数据,弄完一组全清空
            memset(c,0,++cnt*104);
            memset(f,0,cnt<<2);
            memset(e,0,cnt<<2);
            memset(g,0,cnt<<2);
            memset(a,0,++n<<2);
            memset(he,0,n<<2);
            skip;
        }
        fwrite(o,1,p1-o,stdout);//奇技淫巧之fwrite
        return 0;
    }
    
  • 相关阅读:
    python学习(二十三) String(下) 分片和索引
    python学习(二十二) String(上)
    微服务网关
    【转】linux 软连接 硬链接
    设计模式--观察者模式
    设计模式--策略模式
    ubuntu-server 安装redis
    【转】linux的hostname修改详解
    【转】ftp的两种模式
    【转】linux下find查找命令用法
  • 原文地址:https://www.cnblogs.com/flashhu/p/8366363.html
Copyright © 2011-2022 走看看