Wireless Password
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7689 Accepted Submission(s): 2498
Problem Description
Liyuan lives in a old apartment. One day, he suddenly found that there was a wireless network in the building. Liyuan did not know the password of the network, but he got some important information from his neighbor. He knew the password consists only of lowercase letters ‘a’-‘z’, and he knew the length of the password. Furthermore, he got a magic word set, and his neighbor told him that the password included at least k words of the magic word set (the k words in the password possibly overlapping).
For instance, say that you know that the password is 3 characters long, and the magic word set includes ‘she’ and ‘he’. Then the possible password is only ‘she’.
Liyuan wants to know whether the information is enough to reduce the number of possible passwords. To answer this, please help him write a program that determines the number of possible passwords.
Input
There will be several data sets. Each data set will begin with a line with three integers n m k. n is the length of the password (1<=n<=25), m is the number of the words in the magic word set(0<=m<=10), and the number k denotes that the password included at least k words of the magic set. This is followed by m lines, each containing a word of the magic set, each word consists of between 1 and 10 lowercase letters ‘a’-‘z’. End of input will be marked by a line with n=0 m=0 k=0, which should not be processed.
Output
For each test case, please output the number of possible passwords MOD 20090717.
Sample Input
10 2 2
hello
world
4 1 1
icpc
10 0 0
0 0 0
Sample Output
2
1
14195065
Source
2009 Multi-University Training Contest 1 - Host by TJU
Recommend
gaojie | We have carefully selected several similar problems for you: 2819 2824 2817 2823 2822
sol:
十分的惨惨啊,这是我人生中第一次写ac自动机上的dp,除练习外第一次正式接触ac自动机
感觉ac自动机上的dp似乎挺显然的
f[i][j][op]表示主串在i位置,在自动机的j节点,已经码出来了op的串的状态,然后瞎比转移就好了,复杂度O(n*26*m*mlen*2^10)所以就有了第一版最naive的做法
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
const int pyz=20090717;
inline int read()
{
char c;
int res,flag=0;
while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
res=c-'0';
while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
return flag?-res:res;
}
const int N=110;
int tot,l,go[N][26],mark[N],tag[N],top[N][26];
char sr[N];
inline void insert(int &x,int y,int id)
{
if(!x) x=++tot;
if(y>l)
{
mark[x]=1<<id-1;
return;
}
insert(go[x][sr[y]-'a'],y+1,id);
}
int q[N],fail[N];
inline void build()
{
int u,v,t=0,w=1;
q[1]=1;
while(t<w)
{
u=q[++t];
for(int i=0;i<=25;++i)
if(go[u][i])
{
q[++w]=go[u][i];
v=fail[u];
while(!go[v][i]) v=fail[v];
fail[go[u][i]]=go[v][i];
}
}
}
int f[26][N][1025],k;
int main()
{
freopen("2825.in","r",stdin);
// freopen("2825p.out","w",stdout);
while(true)
{
n=read();
m=read();
k=read();
if(!n&&!m&&!k) break;
memset(f,0,sizeof(f));
memset(go,0,sizeof(go));
memset(mark,0,sizeof(mark));
memset(fail,0,sizeof(fail));
tot=1;
int rt=1;
for(int i=1;i<=m;++i)
{
scanf("%s",sr+1);
l=strlen(sr+1);
insert(rt,1,i);
}
for(int i=0;i<=25;++i) go[0][i]=1;
build();
int all=1<<m;
--all;
f[0][1][0]=1;
for(int i=0;i<n;++i)
for(int j=1;j<=tot;++j)
for(int op=0;op<=all;++op)
if(f[i][j][op])
for(int ch=0;ch<=25;++ch)
{
int b,c,d;
// if(j==3&&ch==2)
// cout<<"faq";
b=j;
while(!go[b][ch])
b=fail[b];
b=go[b][ch];
c=op;
d=b;
while(d)
{
c|=mark[d];
d=fail[d];
}
// printf("%d %d %d %d\n",j,ch,b,c);
(f[i+1][b][c]+=f[i][j][op])%=pyz;
}
int ans=0;
for(int j=0;j<=tot;++j)
for(int op=0;op<=all;++op)
if(f[n][j][op])
{
int cnt=0;
for(int i=0;i<m;++i)
if((1<<i)&op) ++cnt;
if(cnt>=k)
(ans+=f[n][j][op])%=pyz;
}
printf("%d\n",ans);
}
}
这个naive的算法t飞了,显然是因为我在dp里跳的原因。注意到跳的这个东西可以预处理,所以记top[u][ch]表示u,ch这个状态最后会跳到哪里,tag[u]表示mark[u]和mark[fail[u]]和mark[fail[fail[u]]]等等的mark值,转移也很simple
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
const int pyz=20090717;
inline int read()
{
char c;
int res,flag=0;
while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
res=c-'0';
while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
return flag?-res:res;
}
const int N=260;
int tot,l,go[N][26],mark[N],tag[N],top[N][26];
char sr[N];
inline void insert(int &x,int y,int id)
{
if(!x) x=++tot;
if(y>l)
{
mark[x]=1<<id-1;
return;
}
insert(go[x][sr[y]-'a'],y+1,id);
}
int q[N],fail[N];
inline void build()
{
int u,v,t=0,w=1;
q[1]=1;
while(t<w)
{
u=q[++t];
for(int i=0;i<=25;++i)
if(go[u][i])
{
q[++w]=go[u][i];
v=fail[u];
while(!go[v][i]) v=fail[v];
fail[go[u][i]]=go[v][i];
}
}
for(int i=0;i<=25;++i) top[0][i]=1;
//必须用bfs序来做,可能fail[u]>u
// for(int d=1;d<=tot;++d)
// tag[d]=mark[d]|tag[fail[d]];
/* for(int b=1;b<=tot;++b)
for(int ch=0;ch<=25;++ch)
{
if(go[b][ch]) top[b][ch]=go[b][ch];
else top[b][ch]=top[fail[b]][ch];
}*/
t=0;
q[w=1]=1;
while(t<w)
{
u=q[++t];
int d=u;
tag[d]=mark[d]|tag[fail[d]];
for(int i=0;i<=25;++i)
if(go[u][i])
{
q[++w]=go[u][i];
int b=u,ch=i;
top[b][ch]=go[b][ch];
}
else top[u][i]=top[fail[u]][i];
}
}
int f[26][N][1025],k;
int main()
{
// freopen("2825.in","r",stdin);
// freopen("2825.out","w",stdout);
while(true)
{
n=read();
m=read();
k=read();
if(!n&&!m&&!k) break;
memset(f,0,sizeof(f));
memset(go,0,sizeof(go));
memset(mark,0,sizeof(mark));
memset(fail,0,sizeof(fail));
memset(top,0,sizeof(top));
memset(tag,0,sizeof(tag));
tot=1;
int rt=1;
for(int i=1;i<=m;++i)
{
scanf("%s",sr+1);
l=strlen(sr+1);
insert(rt,1,i);
}
for(int i=0;i<=25;++i) go[0][i]=1;
build();
int all=1<<m;
--all;
f[0][1][0]=1;
for(int i=0;i<n;++i)
for(int j=1;j<=tot;++j)
for(int op=0;op<=all;++op)
if(f[i][j][op])
for(int ch=0;ch<=25;++ch)
{
int b,c,d;
b=top[j][ch];
c=op|tag[b];
(f[i+1][b][c]+=f[i][j][op])%=pyz;
}
int ans=0;
for(int j=0;j<=tot;++j)
for(int op=0;op<=all;++op)
if(f[n][j][op])
{
int cnt=0;
for(int i=0;i<m;++i)
if((1<<i)&op) ++cnt;
if(cnt>=k)
(ans+=f[n][j][op])%=pyz;
}
printf("%d\n",ans);
}
}
这个代码也可以说是非常的naive了,因为显然两个bfs可以合并
就是写的时候注意一下一定要用bfs来算top和tag
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
using namespace std;
int n,m;
const int pyz=20090717;
inline int read()
{
char c;
int res,flag=0;
while((c=getchar())>'9'||c<'0') if(c=='-')flag=1;
res=c-'0';
while((c=getchar())>='0'&&c<='9') res=(res<<3)+(res<<1)+c-'0';
return flag?-res:res;
}
const int N=260;
int tot,l,go[N][26],mark[N],tag[N],top[N][26];
char sr[N];
inline void insert(int &x,int y,int id)
{
if(!x) x=++tot;
if(y>l)
{
mark[x]=1<<id-1;
return;
}
insert(go[x][sr[y]-'a'],y+1,id);
}
int q[N],fail[N];
inline void build()
{
int u,v,t=0,w=1;
for(int i=0;i<=25;++i) top[0][i]=1;
q[1]=1;
while(t<w)
{
u=q[++t];
int d=u;
tag[d]=mark[d]|tag[fail[d]];
for(int i=0;i<=25;++i)
if(go[u][i])
{
q[++w]=go[u][i];
v=fail[u];
while(!go[v][i]) v=fail[v];
fail[go[u][i]]=go[v][i];
int b=u,ch=i;
top[b][ch]=go[b][ch];
}
else top[u][i]=top[fail[u]][i];
}
}
int f[26][N][1025],k;
int main()
{
// freopen("2825.in","r",stdin);
// freopen("2825.out","w",stdout);
while(true)
{
n=read();
m=read();
k=read();
if(!n&&!m&&!k) break;
memset(f,0,sizeof(f));
memset(go,0,sizeof(go));
memset(mark,0,sizeof(mark));
memset(fail,0,sizeof(fail));
memset(top,0,sizeof(top));
memset(tag,0,sizeof(tag));
tot=1;
int rt=1;
for(int i=1;i<=m;++i)
{
scanf("%s",sr+1);
l=strlen(sr+1);
insert(rt,1,i);
}
for(int i=0;i<=25;++i) go[0][i]=1;
build();
int all=1<<m;
--all;
f[0][1][0]=1;
for(int i=0;i<n;++i)
for(int j=1;j<=tot;++j)
for(int op=0;op<=all;++op)
if(f[i][j][op])
for(int ch=0;ch<=25;++ch)
{
int b,c,d;
b=top[j][ch];
c=op|tag[b];
(f[i+1][b][c]+=f[i][j][op])%=pyz;
}
int ans=0;
for(int j=0;j<=tot;++j)
for(int op=0;op<=all;++op)
if(f[n][j][op])
{
int cnt=0;
for(int i=0;i<m;++i)
if((1<<i)&op) ++cnt;
if(cnt>=k)
(ans+=f[n][j][op])%=pyz;
}
printf("%d\n",ans);
}
}
实际上ac自动机上的dp非常的easy啊,只要记得一次性把u和fail树祖先一起弄了就行,预处理一下一次跳到最后的数组,top和fail[0][x]全部设成1就行了。dp起来什么的全部都很轻松的样子啊(这里的flag我自己记下了啊,接下来可能还会写个2-3天的ac自动机dp)