题目描述:
长度为n<=25的密码,至少包含m<=10个神奇单词中的k个(单词长度<=10),求所有可能的密码个数。
题目分析:
设
f
[
i
]
[
s
]
[
j
]
f[i][s][j]
f[i][s][j]表示用了
i
i
i个字母,已经包含的神奇单词的状态为
s
s
s,当前在AC自动机上的
j
j
j号点的方案数。
转移为
f
[
i
+
1
]
[
s
∣
w
o
r
d
[
c
h
[
j
]
[
c
]
]
]
[
c
h
[
j
]
[
c
]
]
+
=
f
[
i
]
[
s
]
[
j
]
f[i+1][s|word[ch[j][c]]][ch[j][c]]+=f[i][s][j]
f[i+1][s∣word[ch[j][c]]][ch[j][c]]+=f[i][s][j]。复杂度
O
(
n
m
l
e
n
2
m
∗
26
)
O(nmlen2^m*26)
O(nmlen2m∗26),很多状态是0可以跳过。
Code:
#include<cstdio>
#include<cstring>
#define maxn 105
#define maxc 26
const int mod = 20090717;
int n,m,k;
char s[maxn];
struct ac_automaton{
int ch[maxn][maxc],fail[maxn],cnt[maxn],tot,f[1025][maxn],g[1025][maxn];
inline void init(){
memset(ch,0,sizeof ch);memset(cnt,0,sizeof cnt);
memset(f,0,sizeof f);
tot=0;
}
inline void insert(char *s,int id){
int len=strlen(s),r=0,v;
for(int i=0;i<len;r=ch[r][v],i++) if(!ch[r][v=s[i]-'a']) ch[r][v]=++tot;
cnt[r]|=(1<<id);
}
int Q[maxn],head,tail;
inline void build(){
Q[head=0]=0,tail=1;
while(head<tail)
{
int r=Q[head++],c;
for(int i=0;i<maxc;i++)
if(c=ch[r][i]) fail[c]=r?ch[fail[r]][i]:0,Q[tail++]=c,cnt[c]|=cnt[fail[c]];
else ch[r][i]=ch[fail[r]][i];
}
}
inline void add(int &a,const int b){a=(a+b)%mod;}
inline void solve(){
int ans=0;
f[0][0]=1;
for(int l=1;l<=n;l++)
{
memset(g,0,sizeof g);
for(int s=0;s<(1<<m);s++)
for(int i=0;i<=tot;i++) if(f[s][i])
for(int j=0;j<maxc;j++)
add(g[s|cnt[ch[i][j]]][ch[i][j]],f[s][i]);
memcpy(f,g,sizeof f);
}
for(int s=0;s<(1<<m);s++){
int x=0;
for(int i=0;i<m;i++) if(s&(1<<i)) x++;
if(x>=k) for(int i=0;i<=tot;i++) add(ans,f[s][i]);
}
printf("%d\n",ans);
}
}A;
int main()
{
while(scanf("%d%d%d",&n,&m,&k),n)
{
A.init();
for(int i=0;i<m;i++) scanf("%s",s),A.insert(s,i);
A.build();
A.solve();
}
}