• P4052 [JSOI2007]文本生成器


    传送门

    AC自动机

    考虑一位一位填字符

    一旦包含了单词(即经过了结束标记或fail上的结束标记)

    那么后面不管填什么都是一篇可读的文章

    如果一共要填 m 个单词,当前填到了第 i 个字符就一定可读了

    那么后面每个字符都有 26 种填法,所以方案就多了 26^(m-i) 种

    然后打了个 30 分的搜索

    在AC自动机上走,一旦有匹配就更新答案,不用再下去搜,返回找其他方案

    其他TLE

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    using namespace std;
    const int mo=10007;
    const int N=1e5+7;
    int n,m,ans;
    int c[N][27],fail[N],cnt;
    bool pd[N];
    char a[N];
    inline void ins()
    {
        int u=0,len=strlen(a);
        for(int i=0;i<len;i++)
        {
            int v=a[i]-'A'+1;
            if(!c[u][v]) c[u][v]=++cnt;
            u=c[u][v];
        }
        pd[u]=1;
    }
    queue <int> qa;
    inline void pre()
    {
        for(int i=1;i<=26;i++) if(c[0][i]) qa.push(c[0][i]);
        while(!qa.empty())
        {
            int u=qa.front(); qa.pop();
            for(int i=1;i<=26;i++)
            {
                int v=c[u][i];
                if(!v) c[u][i]=c[fail[u]][i];
                else
                {
                    fail[v]=c[fail[u]][i];
                    pd[v]|=pd[fail[v]];//只要fail上有结束标记,当前节点也包含可读单词
                    qa.push(v);
                }
            }
        }
    }
    inline int ksm(int x)
    {
        int res=1,a=26;
        while(x)
        {
            if(x&1) res=res*a%mo;
            a=a*a%mo; x>>=1;
        }
        return res;
    }
    void dfs(int stp,int pos)//已经填了stp个单词,当前在自动机的pos位置上
    {
        if(pd[pos])//如果包含可读单词直接更新答案并返回
        {
            ans=(ans+ksm(m-stp))%mo;
            return;
        }
        if(stp==m) return;//边界
        for(int i=1;i<=26;i++)
        {
            int v=c[pos][i];
            dfs(stp+1,v);//向下填单词
        }
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        {
            scanf("%s",a);
            ins();
        }
        pre();
        dfs(0,0);
        cout<<ans;
        return 0;
    }
    30分

    考虑把搜索换成DP

    设 f[ i ] [ j ] 表示填了第 i 个单词,当前在AC自动机的 j 位置上有几种方案

    转移显然,f[ i+1 ] [ k ] += f [ i ] [ j ] ( j 可以走到 k )

    如果 j 有包含可读单词,那么 ans += f[ i ] [ j ] * 26^(m-i),并且这个状态不用转移到后面的状态

    然后是代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    using namespace std;
    const int mo=10007;
    const int N=1e5+7;
    int n,m,ans;
    int c[N][27],fail[N],cnt;
    bool pd[N];
    char a[N];
    inline void ins()
    {
        int u=0,len=strlen(a);
        for(int i=0;i<len;i++)
        {
            int v=a[i]-'A'+1;
            if(!c[u][v]) c[u][v]=++cnt;
            u=c[u][v];
        }
        pd[u]=1;
    }
    queue <int> qa;
    inline void pre()
    {
        for(int i=1;i<=26;i++) if(c[0][i]) qa.push(c[0][i]);
        while(!qa.empty())
        {
            int u=qa.front(); qa.pop();
            for(int i=1;i<=26;i++)
            {
                int v=c[u][i];
                if(!v) c[u][i]=c[fail[u]][i];
                else
                {
                    fail[v]=c[fail[u]][i];
                    pd[v]|=pd[fail[v]];//只要fail上有结束标记,当前也包含可读单词
                    qa.push(v);
                }
            }
        }
    }
    inline int ksm(int x)
    {
        int res=1,a=26;
        while(x)
        {
            if(x&1) res=res*a%mo;
            a=a*a%mo; x>>=1;
        }
        return res;
    }
    int f[107][N];
    void slove()
    {
        for(int i=1;i<=26;i++) f[1][c[0][i]]++;//初始状态
        for(int i=1;i<=m;i++)//填第i位
        {
            for(int j=0;j<=cnt;j++)//在自动机上的第j个位置
            {
                if(!f[i][j]) continue;
                if(pd[j])//如果有包含单词
                {
                    ans=(ans+f[i][j]*ksm(m-i)%mo)%mo;//更新答案
                    continue;
                }
                for(int k=1;k<=26;k++)//负责把状态向下一步转移
                {
                    int v=c[j][k];
                    f[i+1][v]=(f[i+1][v]+f[i][j])%mo;
                }
            }
        }
    }
    int main()
    {
        cin>>n>>m;
        for(int i=1;i<=n;i++)
        {
            scanf("%s",a);
            ins();
        }
        pre();
        slove();
        cout<<ans;
        return 0;
    }
  • 相关阅读:
    Servlet--j2e中文乱码解决
    python 通过ftplib 实现上传下载
    使用sqlyog将sql server 迁移到mysql
    c++
    二进制(signed or unsigned)补码
    sql 2012先分离迁移mdf mlf 文件到别的机器后附加 数据库成只读的修复方法
    events
    NoSQL文章
    程序员如何成功的假装在很努力的工作(转)
    如何成为强大的程序员(转)
  • 原文地址:https://www.cnblogs.com/LLTYYC/p/9689605.html
Copyright © 2020-2023  润新知