BZOJ 1559: [JSOI2009]密码(AC自动机+状压DP)

题目描述

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

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

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

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

输入格式

输入数据首先输入两个整数L,N,分别表示密码的长度与观察到子串的个数。

接下来N行,每行若干个字符,描述了每个观察到的字符串。

输出格式

输出数据第一行为一个整数,代表了满足所有观察条件字符串的总数。

若这个数字小于等于42,则按字典顺序输出所有密码的可能情况,每行一个,否则,只输出满足所有观察条件字符串的总数即可。


题解:
看数据范围应该就能看出来是状压DP,而且最后又要包含所有的单词,那么我们可以直接设dp[i][j][S] 表示已经处理到的长度为i,在AC自动机上j结点上,已经包含的单词数量为S的方案数。

然后考虑一个问题,如果一个字符串是另一个字符串的子串呢?比如处理了’abc‘,里面已经包含了串‘bc’,而我们这样状压是会认为没有出现’bc’的,但实际上它是出现过的,因此我们需要对互相包含的字符串进行一个去重,将重复的字符串直接删掉,然后统计答案的时候就记录没有被删掉的单词的集合state就好…

然后就是输出方案直接预处理出每两个字符串的重叠部分,然后爆搜即可


AC代码:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#include<ext/rope>
using namespace std;
using namespace __gnu_cxx;
#define LL long long
#define pii pair<int,int>
#define mp(a,b) make_pair(a,b)
const int MAXN = 55;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;
char a[MAXN][MAXN],ans[MAXN][MAXN];
LL dp[2][150][(1<<10)+20];
int len[MAXN],bor[MAXN][MAXN],nxt[150][26],fail[150],sta[150];
int del[MAXN],g[MAXN],vis[MAXN],id[MAXN],L,n,m,ans_tot,cnt,tot;
inline void Insert(int x){
    int rt=0;
    for(int i=1;i<=len[x];i++){
        if(!nxt[rt][a[x][i]-'a']) nxt[rt][a[x][i]-'a']=++tot;
        rt = nxt[rt][a[x][i]-'a'];
    }
    sta[rt]=(1<<(x-1));
}
inline void Build(){
    queue<int> que;
    for(int i=0;i<26;i++) if(nxt[0][i]) que.push(nxt[0][i]);
    while(!que.empty()){
        int u=que.front(); que.pop();
        for(int i=0;i<26;i++){
            if(nxt[u][i]){
                fail[nxt[u][i]]=nxt[fail[u]][i];
                que.push(nxt[u][i]);
            }else{
                nxt[u][i]=nxt[fail[u]][i];
            }
        }
    }
}
inline int border(int x,int y){//计算x,y串的重叠部分
    for(int i=min(len[x],len[y]);i>=0;i--){//枚举重叠的长度判断即可
        bool ok = true;
        for(int j=1;j<=i;j++)
            if(a[x][len[x]-i+j]!=a[y][j])
                { ok=false; break; }
        if(ok) return i;
    }
}
inline bool check(int x,int y){//判断x是否为y的子串
    for(int i=0;i<=len[y]-len[x];i++){
        bool ok = true;
        for(int j=1;j<=len[x];j++)
            if(a[x][j]!=a[y][i+j])
                { ok=false; break; }
        if(ok) return true;
    }
    return false;
}
inline void unique(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(len[j]>len[i] && !del[j] && !del[i] && check(i,j))
                del[i]=1,m--;//如果i串是j串的子串,就把i串删掉
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            bor[i][j]=border(i,j);//暴力处理出每两个字符串的重叠部分
}
void dfs(int x){//爆搜即可
    if(x>m){
        cnt++; int t=0;
        for(int i=1;i<=ans_tot;i++)
            for(int j=bor[g[i-1]][g[i]]+1;j<=len[g[i]];j++)
                ans[cnt][++t]=a[g[i]][j];
        if(t!=L) cnt--;
        return;
    }
    for(int i=1;i<=n;i++){
        if(!del[i] && !vis[i]){
            vis[i]=1; g[++ans_tot]=i;
            dfs(x+1);
            vis[i]=0; --ans_tot;
        }
    }
}
inline bool cmp(int x,int y){//按字典序排序
    for(int i=1;i<=L;i++)
        if(ans[x][i]!=ans[y][i])
            return ans[x][i]<ans[y][i];
    return false;
}
signed main(){
#ifndef ONLINE_JUDGE
    freopen("C:\\Users\\Administrator\\Desktop\\in.txt","r",stdin);
#endif // ONLINE_JUDGE
    scanf("%d%d",&L,&m); n = m;
    for(int i=1;i<=m;i++) scanf("%s",a[i]+1),len[i]=strlen(a[i]+1);
    unique();
    for(int i=1;i<=n;i++) if(!del[i]) Insert(i);
    Build();
    dp[0][0][0]=1;
    for(int i=1;i<=L;i++){//BZOJ内存限制小,需要滚动数组优化,洛谷不用
        memset(dp[i&1],0,sizeof(dp[i&1]));
        for(int j=0;j<=tot;j++)
            for(int s=0;s<(1<<n);s++)
                if(dp[(i+1)&1][j][s])
                    for(int k=0;k<26;k++)
                        dp[i&1][nxt[j][k]][s|sta[nxt[j][k]]] += dp[(i+1)&1][j][s];
    }
    LL res=0,s=0;
    for(int i=1;i<=n;i++) if(!del[i]) s+=(1<<(i-1));//只统计没被删掉的单词
    for(int i=0;i<=tot;i++) res+=dp[L&1][i][s];
    printf("%lld\n",res);
    if(res<=42){
        dfs(1);
        for(int i=1;i<=cnt;i++) id[i]=i;
        sort(id+1,id+cnt+1,cmp);
        for(int i=1;i<=cnt;i++){
            for(int j=1;j<=L;j++) putchar(ans[id[i]][j]);
            puts("");
        }
    }
    return 0;
}

发布了152 篇原创文章 · 获赞 1 · 访问量 2696

猜你喜欢

转载自blog.csdn.net/qq_43544481/article/details/103883807