BZOJ1559 [JSOI2009]密码

题意

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

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

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

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

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

分析

参照LadyLex的题解。

这道题是我今天刷到的比较好的一道题……不仅考到了dp,还考到了搜索基本功。

我们首先考虑:对于串\(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的逆过程,看合法方案由哪个节点转移过来,并且记录一路上经过的字符,最后排序输出即可。

这真是一道很不错的题目,方式以及套路很经典,对于状压和搜索的应用都很灵活!

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

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
    rg T data=0;
    rg int w=1;
    rg char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            w=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        data=data*10+ch-'0';
        ch=getchar();
    }
    return data*w;
}
template<class T>il T read(rg T&x)
{
    return x=read<T>();
}
typedef long long ll;

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;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/10321235.html
今日推荐