密码

众所周知,密码在信息领域起到了不可估量的作用。对于普通的登陆口令,唯一的破解 方法就是暴力破解一逐个尝试所有可能的字母组合,但这是一项很耗时又容易被发现的工 作。所以,为了获取对方的登陆口令,在暴力破解密码之前,必须先做大量的准备工作。经 过情报的搜集,现在得到了若干有用信息,形如:

“我观察到,密码中含有字符串***。”

例如,对于一个10位的密码以及串hello与world,可观察到的字符能的密码组合为 helloworld与worldhello;而对于6位的密码以及观察到的字符串good与day,可能的 密码组合为gooday。

有了这些信息,就能够大大地减少尝试的次数了。请编一个程序,计算所有密码组合的可能。密码中仅可能包含a - z之间的小写字母。

对于100%的数据,1<=L<=25,1<=N<=10,每个观察到的字符串长不超过10,并且保证输出结果小于2^63。

LadyLex的题解

我们首先考虑:对于串\(i\)和\(j\),如果\(j\)是\(i\)的子串,那么我们根本不用考虑最初单独插入进来的\(j\)串,因为只要\(i\)串存在,\(j\)串就一定存在

那么我们可以在构建出AC自动机之后,把每个节点从fail指针能达到的节点都设为”不是单词节点“,最后再给单词节点重新编号即可。

那么接下来,我们考虑dp的过程。由于节点数,串数和串长都很小,所以我们考虑状态压缩来解决这个问题。

我们定义状态数组\(f[i][j][k]\)表示当前串长为\(i\),位于\(j\)号节点,模式串出现情况为\(k\)的方案数。

(这种"走\(i\)步到达\(j\)节点”也是AC自动机上的常见套路之一)

那么我们事先把单词节点对应的串用二进制压好,转移到时候我们只需要这样处理即可:

f[i+1][ch[j][u]][k|val[ch[j][u]]]+=f[i][j][k];

这样我们就可以搜出方案数,接下来我们考虑输出小于42的具体方案。

首先我们可以得到一个性质:若总方案数不超过42,那么最终串一定仅由给定串拼接而成。

因为如果随机字母可以存在,哪怕只有1个模式串,并且仅有1个随机字母,合法方案数在这种最小情况下也有2×26=52种>42

因此我们只需要用搜索进行一个dp的逆过程,看合法方案由哪个节点转移过来,并且记录一路上经过的字符,最后排序输出即可。

这真是一道很的题目,方式以及套路很经典,对于状压和搜索的应用都很灵活!
UPD:这题强行组合了两种套路。

时间复杂度\(O(26 L N^2 2^N)\),算出来是7e7。

co int N=11,L=26,K=(1<<10)+10;
int l,n;
char s[N][N];
namespace AC
{
    int tot,num;
    int ch[N*N][26],fail[N*N];
    int val[N*N],meaning[N*N];

    void ins(char s[],int n)
    {
        int u=0;
        for(int i=0;i<n;++i)
        {
            int k=s[i]-'a';
            if(!ch[u][k])
                ch[u][k]=++tot;
            u=ch[u][k],meaning[u]=k;
        }
        val[u]=1;
    }

    void getfail()
    {
        std::queue<int>Q;
        for(int i=0;i<26;++i)
            if(ch[0][i])
                Q.push(ch[0][i]);
        while(Q.size())
        {
            int u=Q.front();Q.pop();
            for(int i=0;i<26;++i)
            {
                if(ch[u][i])
                {
                    fail[ch[u][i]]=ch[fail[u]][i];
                    Q.push(ch[u][i]);
                }
                else
                    ch[u][i]=ch[fail[u]][i];
            }
        }
        for(int i=1;i<=tot;++i)
            for(int u=fail[i];u;u=fail[u])
                val[u]=0;
        for(int i=1;i<=tot;++i)
            if(val[i])
                val[i]=(1<<num++);
    }

    ll f[L][N*N][K];
    struct sol
    {
        char s[L];

        sol()
        {
            memset(s,0,sizeof s);
        }

        void print()
        {
            puts(s);
        }

        bool operator<(co sol&b)co
        {
            for(int i=0;i<l;++i)
                if(s[i]!=b.s[i])
                    return s[i]<b.s[i];
            return 0;
        }
    }stack;
    std::vector<sol>str;

    void dfs(int len,int i,int state,int now)
    {
        stack.s[len-1]=now+'a';
        if(len==1)
        {
            str.push_back(stack);
            return;
        }
        for(int j=0;j<=tot;++j)
            if(f[len-1][j][state]&&ch[j][now]==i)
                dfs(len-1,j,state,meaning[j]);
        if(val[i])
            for(int j=0;j<=tot;++j)
                if(f[len-1][j][state^val[i]]&&ch[j][now]==i)
                    dfs(len-1,j,state^val[i],meaning[j]);
    }

    void getsolution()
    {
        for(int i=1;i<=tot;++i)
            if(f[l][i][(1<<num)-1])
                dfs(l,i,(1<<num)-1,meaning[i]);
    }

    void solve()
    {
        f[0][0][0]=1;
        for(int i=0;i<l;++i)
            for(int j=0;j<=tot;++j)
                for(int k=0;k<(1<<num);++k)
                    if(f[i][j][k])
        for(int u=0;u<26;++u)
            f[i+1][ch[j][u]][k|val[ch[j][u]]]+=f[i][j][k];
        ll ans=0;
        for(int j=0;j<=tot;++j) // edit 1:0
            ans+=f[l][j][(1<<num)-1];
        printf("%lld\n",ans);
        if(ans<=42)
        {
            getsolution();
            sort(str.begin(),str.end());
            assert(str.size()==ans);
            for(int i=0;i<ans;++i)
                str[i].print();
        }
    }
}

int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    read(l),read(n);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",s[i]);
        AC::ins(s[i],strlen(s[i]));
    }
    AC::getfail();
    AC::solve();
    return 0;
}

文本生成器

给出若干个由大写字母构成的单词,问长度为 m ,由大写字母构成的字符串中,包含至少一个单词的数目.对 10007 取模.

jklover的题解

可以先求出不包含任意一个单词的字符串数目,再用总数目26m减去.

将单词建成一个 AC 自动机,类似上题,合并权值即可求出一个节点是否能被走到.

用 \(f[i][j]\) 表示已经走了 \(i\) 步,走到了节点 \(j\) 时的方案数. \(O(n^2)\) dp 即可.

AC自动机上面dp才是AC自动机的精髓。

co int mod=1e4+7;

int add(int x,int y)
{
    return (x+y)%mod;
}

int mul(int x,int y)
{
    return x*y%mod;
}

int qpow(int x,int k)
{
    int res=1;
    while(k)
    {
        if(k&1)
            res=mul(res,x);
        x=mul(x,x),k>>=1;
    }
    return res;
}

co int N=7777,S=26;
int n,m;
namespace AC
{
    int idx;
    int ch[N][S],fail[N],val[N];
    int f[101][N];

    void init()
    {
        memset(f,-1,sizeof f);
    }

    void ins(char*s,int len)
    {
        int u=0;
        for(int i=0;i<len;++i)
        {
            int k=s[i]-'A';
            if(!ch[u][k])
                ch[u][k]=++idx;
            u=ch[u][k];
        }
        val[u]=1;
    }

    void getfail()
    {
        std::queue<int>Q;
        for(int i=0;i<S;++i)
            if(ch[0][i])
                Q.push(ch[0][i]);
        while(Q.size())
        {
            int u=Q.front();Q.pop();
            for(int i=0;i<S;++i)
            {
                if(ch[u][i])
                {
                    fail[ch[u][i]]=ch[fail[u]][i];
                    Q.push(ch[u][i]);
                }
                else
                    ch[u][i]=ch[fail[u]][i];
            }
            val[u]|=val[fail[u]];
        }
    }

    int dfs(int i,int j)
    {
        if(f[i][j]!=-1)
            return f[i][j];
        if(val[j])
            return 0;
        if(i==m)
            return 1;
        int&res=f[i][j]=0;
        for(int k=0;k<S;++k)
            res=add(res,dfs(i+1,ch[j][k]));
        return res;
    }

    void solve()
    {
        int ans=qpow(26,m);
        ans=add(ans,mod-dfs(0,0));
        printf("%d\n",ans);
    }
}
char buf[N];

int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    AC::init();
    read(n),read(m);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",buf);
        AC::ins(buf,strlen(buf));
    }
    AC::getfail();
    AC::solve();
    return 0;
}

Rescue the Rabbit

现在有n个基因片段(用包含A、G、T、C的字符串表示),每个基因片段有一个权值,现在求长为L的基因的最大权值(每个基因片段重复出现算一次,不用计算多次)?

n (1 ≤ n ≤ 10),l (1 ≤ l ≤ 100)

分析

未知定长串中不同已知模板串的出现次数问题,一般做法是AC自动机上dp。

考虑背包,\(dp(i,j,k)\)表示当前串长为\(i\),在AC自动机上对应节点\(j\),已匹配的模板串的状态为\(k\)的情况是否出现。用刷表法向后转移。先枚举不定串长度,再枚举AC自动机上节点,然后枚举已知状态,最后枚举字母边转移。

时间复杂度\(O(l \cdot MaxNode \cdot 2^n \cdot SigmaSize)\)。第一维可以滚动,空间复杂度\(O(MaxNode \cdot 2^n)\)

const int MAXN=1010;
const int SigmaSize=4;

bool dp[2][MAXN][1100];
int mp[15];

struct Trie
{
    int next[MAXN][SigmaSize];
    int fail[MAXN];
    int end[MAXN];
    int root,ncnt;
    int newnode()
    {
        for(int i=0;i<SigmaSize;++i)
            next[ncnt][i]=-1;
        end[ncnt++]=0;
        return ncnt-1;
    }
    void init()
    {
        ncnt=0;
        root=newnode();
    }
    int id(char c)
    {
        if(c=='A')
            return 0;
        else if(c=='G')
            return 1;
        else if(c=='T')
            return 2;
        else
            return 3;
    }
    void insert(char*str,int v)
    {
        int now=root;
        int len=strlen(str);
        for(int i=0;i<len;++i)
        {
            int c=id(str[i]);
            if(next[now][c]==-1)
                next[now][c]=newnode();
            now=next[now][c];
        }
        end[now]|=(1<<v);
    }
    void getfail()
    {
        queue<int>Q;
        fail[root]=root;
        for(int i=0;i<SigmaSize;++i)
        {
            if(next[root][i]==-1)
                next[root][i]=root;
            else
            {
                fail[next[root][i]]=root;
                Q.push(next[root][i]);
            }
        }
        while(!Q.empty())
        {
            int now=Q.front();
            Q.pop();
            end[now]|=end[fail[now]];
            for(int i=0;i<SigmaSize;++i)
            {
                if(next[now][i]==-1)
                    next[now][i]=next[fail[now]][i];
                else
                {
                    fail[next[now][i]]=next[fail[now]][i];
                    Q.push(next[now][i]);
                }
            }
        }
    }
    void solve(int n,int l)
    {
        memset(dp,0,sizeof(dp));
        dp[0][0][0]=1; // dp[len][node][state]
        int cur=1;
        for(int i=1;i<=l;++i)
        {
            memset(dp[cur],0,sizeof(cur));
            for(int j=0;j<ncnt;++j)
            {
                for(int k=0;k<(1<<n);++k)
                {
                    for(int q=0;q<SigmaSize;++q)
                    {
                        int nxt=next[j][q];
                        dp[cur][nxt][k|end[nxt]]=(dp[cur][nxt][k|end[nxt]]||dp[cur^1][j][k]);
                    }
                }
            }
            cur^=1;
        }
        int ans=-INF;
        for(int i=0;i<ncnt;++i)
            for(int j=0;j<(1<<n);++j)
            {
                if(dp[cur^1][i][j])
                {
                    int sum=0;
                    for(int k=0;k<n;++k)
                        if(j&(1<<k))
                            sum+=mp[k];
                    ans=max(ans,sum);
                }
            }
        if(ans<0)
            printf("No Rabbit after 2012!\n");
        else
            printf("%d\n",ans);
    }
}AC;

char s[110];

int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    int n,l,w;
    while(~scanf("%d %d",&n,&l))
    {
        AC.init();
        for(int i=0;i<n;++i)
        {
            scanf("%s %d",s,&w);
            AC.insert(s,i);
            mp[i]=w;
        }
        AC.getfail();
        AC.solve(n,l);
    }
//  fclose(stdin);
//  fclose(stdout);
    return 0;
}

JSOI2009 密码 和 JSOI2007 文本生成器 和 ZOJ3545 Rescue the Rabbit的更多相关文章

  1. BZOJ 1030: [JSOI2007]文本生成器 [AC自动机 DP]

    1030: [JSOI2007]文本生成器 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 3953  Solved: 1614[Submit][Stat ...

  2. bzoj1030 [JSOI2007]文本生成器

    1030: [JSOI2007]文本生成器 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 2654  Solved: 1100[Submit][Stat ...

  3. JSOI2007文本生成器

    1030: [JSOI2007]文本生成器 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 1613  Solved: 656[Submit][Statu ...

  4. BZOJ 1030 [JSOI2007]文本生成器

    1030: [JSOI2007]文本生成器 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 2624  Solved: 1087[Submit][Stat ...

  5. 2781: [JSOI2007]文本生成器

    2781: [JSOI2007]文本生成器 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 8  Solved: 4[Submit][Status][We ...

  6. BZOJ_1030_[JSOI2007]文本生成器_AC自动机+DP

    BZOJ_1030_[JSOI2007]文本生成器_AC自动机+DP Description JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群, 他 ...

  7. P4052 [JSOI2007]文本生成器

    P4052 [JSOI2007]文本生成器 AC自动机+dp 优秀题解传送门 设f[ i ][ j ]表示串的长度为 i ,当前在 j 点时不可识别的串的方案数 最后用总方案数减去不可识别方案数就是答 ...

  8. 【BZOJ1030】[JSOI2007]文本生成器 AC自动机+动态规划

    [BZOJ1030][JSOI2007]文本生成器 Description JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文 ...

  9. [JSOI2007]文本生成器 --- AC自动机 + DP

    [JSOI2007]文本生成器 题目描述: JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版. 该软件可以随机 ...

随机推荐

  1. 如何优雅的处理 async/await 异常

    参考链接:https://cloud.tencent.com/developer/article/1470715 参考链接:https://www.jianshu.com/p/2935c0330dd2

  2. centos7 install docker

    sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo y ...

  3. [转帖]Dockerfile: ENTRYPOINT和CMD的区别

    Dockerfile: ENTRYPOINT和CMD的区别 https://zhuanlan.zhihu.com/p/30555962 在我们查阅Dockerfile的官方文档时, 有可能发现一些命令 ...

  4. java当中JDBC当中请给出一个sql server的helloworld例子

    [学习笔记] 1.sql server的helloworld例子: import java.sql.*; public class JdbcHelloSqlServer {  public stati ...

  5. 在CentOS7上安装OpenJDK1.8 & OracleJDK1.8

    安装OpenJDK1.8 : 1.检查当前机器是否有自带的JDK rpm -qa |grep java rpm -qa |grep jdk rpm -qa |grep gcj 2.如果没有 则跳至安装 ...

  6. 使用Lombok总结

    Lombok学习总结 Project Lombok is a java library that automatically plugs into your editor and build tool ...

  7. 数据结构:队列queue 函数push() pop size empty front back

    队列queue: push() pop() size() empty() front() back() push()  队列中由于是先进先出,push即在队尾插入一个元素,如:可以输出:Hello W ...

  8. 机器学习-EM算法-GMM模型笔记

    GMM即高斯混合模型,下面根据EM模型从理论公式推导GMM: 随机变量X是有K个高斯分布混合而成,取各个高斯分布的概率为φ1,φ2,... ,φK,第i个高斯分布的均值为μi,方差为Σi.若观测到随机 ...

  9. Kafka 的这些原理你知道吗

    如果只是为了开发 Kafka 应用程序,或者只是在生产环境使用 Kafka,那么了解 Kafka 的内部工作原理不是必须的.不过,了解 Kafka 的内部工作原理有助于理解 Kafka 的行为,也利用 ...

  10. shellexecute的使用和X64判断

    bool RunConsoleAsAdmin(std::string appPath, std::string param, bool wait) { LOG_INFO << " ...