题意:
给出密码的长度n,可能含有密码字串的个数m和密码至少含有密码字串的个数k,求有多少种情况。
分析:
因为这个题不是问的密码字串必须全部包含,所以不能矩阵加速= =
果然n的大小变得很小只有25
可以用状压DP来做,具体是每个AC自动机内的节点都编个号,然后getfail的时候像以前矩阵加速getfail一样,假设当前节点的编号是2^k,当前节点的fail指向的点的编号是2^j,那么就把当前节点的编号更新成2^k+2^j(以前矩阵加速是fail节点不能选当前节点也不能选)。
然后dp[i][j][k]
表示走到密码的第i位,在AC自动机上匹配到第j个点,状态为k的方案数。
dp[i][j][k]
就可以推向dp[i+1][jj][k|cnt[jj]]
(cnt数组存放节点编号)。
竟然1A了,爽啊。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int a,b,c,mod=20090717;
char s[30];
struct ACautomata{
int next[255][26],fail[255],cnt[255],dp[26][255][1<<10],bitcnt[1<<10],num,root;
int newnode()
{
memset(next[num],0,sizeof next[num]);
cnt[num]=0;
return num++;
}
void init()
{
num=0;
root=newnode();
bitcnt[0]=0;
for(int i=1;i<(1<<10);++i)
bitcnt[i]=bitcnt[i>>1]+(i&1);
}
void insert(char *s,int l)
{
int len=strlen(s),cur=root;
for(int i=0;i<len;++i)
{
int &tmp=next[cur][s[i]-'a'];
if(!tmp)tmp=newnode();
cur=tmp;
}
cnt[cur]=l;
}
void getfail()
{
queue<int>q;
fail[root]=root;
for(int i=0;i<26;++i)
{
int u=next[root][i];
if(u)
{
fail[u]=0;
q.push(u);
}
else next[root][i]=0;
}
while(!q.empty())
{
int cur=q.front();
q.pop();
for(int i=0;i<26;++i)
{
int u=next[cur][i];
if(u)
{
fail[u]=next[fail[cur]][i];
cnt[u]|=cnt[fail[u]];
q.push(u);
}
else next[cur][i]=next[fail[cur]][i];
}
}
}
int work(int n,int m,int k)
{
for(int i=0;i<=n;++i)
for(int j=0;j<num;++j)
for(int p=0;p<(1<<m);++p)
dp[i][j][p]=0;
dp[0][0][0]=1;
for(int i=0;i<n;++i)
for(int j=0;j<num;++j)
for(int p=0;p<(1<<m);++p)
{
if(dp[i][j][p]==0)continue;
for(int l=0;l<26;++l)
{
int u=next[j][l];
(dp[i+1][u][p|cnt[u]]+=dp[i][j][p])%=mod;
}
}
int res=0;
for(int i=0;i<num;++i)
for(int j=0;j<(1<<m);++j)
if(bitcnt[j]>=k)(res+=dp[n][i][j])%=mod;
return res;
}
}ac;
int main()
{
while(~scanf("%d%d%d",&a,&b,&c)&&(a+b+c))
{
ac.init();
for(int i=0;i<b;++i)
scanf("%s",s),ac.insert(s,1<<i);
ac.getfail();
printf("%d\n",ac.work(a,b,c));
}
}