洛谷 P-4045 密码
记AC的第一道黑题!
题意:已知一段密码包含了一些字符串,然后求满足条件的密码有多少个,数量小于42时还得全部输出
思路:
- 一开始WA了两个点,不知道WA的什么,索性把读入的字符串去重了,实际上不需要
- 建好AC自动机,同时记录当前点是哪个字符串的结尾,方便状态压缩
- 做一遍数位DP,如果所有状态(给的字符串)都能达到,则说明所构造出的密码有效
- 需要注意的是,加入一个字符后,整条fail边上的状态都能达到了,因为fail边上的点代表是当前点的后缀字符串(这就是WA的那两个点)
- 最后考虑小于42时的输出,显然再跑一边相同的dfs不就行了吗?把构造出来的密码放到set里面,完事
题目描述
众所周知,密码在信息领域起到了不可估量的作用。对于普通的登陆口令以,唯一的破解方法就是暴力破解——逐个尝试所有可能的字母组合,但这是一项很耗时又容易被发现的工作。所以,为了获取对方的登陆口令,在暴力破解密码之前,必须先做大量的准备工作。经过情报的搜集,现在得到了若干有用信息,形如:
“我观察到,密码中含有字符串*。”
例如,对于一个10位的密码以及观察到的字符串hello与world,可能的密码组合为helloworld与worldhello;而对于6位的密码以及到的字符串good与day,可能的密码组合为gooday。
有了这些信息,就能够大大地减少尝试的次数了。请编一个程序,计算所有密码组合的可能。密码中仅可能包含a-z之间的小写字母。
输入格式
输入数据首先输入两个整数L,N,分别表示密码的长度与观察到子串的个数。
接下来N行,每行若干个字符,描述了每个观察到的字符串。
输出格式
输出数据第一行为一个整数,代表了满足所有观察条件字符串的总数。
若这个数字小于等于42,则按字典顺序输出所有密码的可能情况,每行一个,否则,只输出满足所有观察条件字符串的总数即可。
输入输出样例
输入
10 2
hello
world
输出
2
helloworld
worldhello
说明/提示
对于100%的数据,1<=L<=25,1<=N<=10,每个观察到的字符串长不超过10,并且保证输出结果小于2^63。
//#pragma comment(linker, "/STACK:102400000,102400000")
#include "bits/stdc++.h"
#define pb push_back
#define ls l,m,now<<1
#define rs m+1,r,now<<1|1
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}
const int maxn = 95+10;
const int mod = 1e9+7;
const double eps = 1e-7;
int L, N;
char s[15];
int trie[maxn][26], fail[maxn], ed[maxn], cnt;
ll dp[26][maxn][4200];
void insert(int id) {
int len=strlen(s), p=0;
for(int i=0; i<len; ++i) {
int k=s[i]-'a';
if(!trie[p][k]) trie[p][k]=++cnt;
p=trie[p][k];
}
ed[p]=id;
}
void build() {
queue<int> q;
for(int i=0; i<26; ++i) if(trie[0][i]) q.push(trie[0][i]);
while(q.size()) {
int now=q.front(); q.pop();
for(int i=0; i<26; ++i) {
if(trie[now][i]) {
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else trie[now][i]=trie[fail[now]][i];
}
}
}
ll dfs(int pos, int now, int state) {
if(pos>L) {
for(int i=1; i<=N; ++i) if(!(state>>i&1)) return 0;
return 1;
}
if(~dp[pos][now][state]) return dp[pos][now][state];
ll ans=0;
for(int i=0; i<26; ++i) {
int newstate=state;
for(int j=trie[now][i]; j; j=fail[j]) newstate|=1<<ed[j];
ans+=dfs(pos+1,trie[now][i],newstate);
}
return dp[pos][now][state]=ans;
}
priority_queue<string,vector<string>,greater<string> > possible;
void dfs1(int pos, int now, int state, string l) {
if(pos>L) {
for(int i=1; i<=N; ++i) if(!(state>>i&1)) return;
possible.push(l);
return;
}
if(!dp[pos][now][state]) return;
for(int i=0; i<26; ++i) {
int newstate=state;
for(int j=trie[now][i]; j; j=fail[j]) newstate|=1<<ed[j];
dfs1(pos+1,trie[now][i],newstate,l+char('a'+i));
}
}
int main() {
//ios::sync_with_stdio(false);
L=read(), N=read();
set<string> sset;
string l;
for(int i=1; i<=N; ++i) cin>>l, sset.insert(l);
int tot=0;
for(auto p: sset) {
for(int i=0; i<p.size(); ++i) s[i]=p[i];
s[p.size()]=0;
insert(++tot);
}
N=tot;
build();
memset(dp,-1,sizeof(dp));
ll ans=dfs(1,0,0);
printf("%lld\n", ans);
if(ans<=42) {
dfs1(1,0,0,"");
while(possible.size()) { cout<<possible.top()<<endl; possible.pop(); }
}
}