HDU2825 Wireless Password(AC自动机+状压DP)
题目问长度n至少包含k个咒语的字符串有多少个。也是比较入门的题。。
- dp[i][j][S]表示长度i(在自动机上转移k步)且后缀状态为自动机上第j个结点且当前包含咒语集合为S的方案数
- dp[0][0][0]=1
- 还是用我为人人转移,AC自动机上的结点要多一个域表示这个结点所代表咒语前缀包含的咒语集合。
1 #include<cstdio> 2 #include<cstring> 3 #include<queue> 4 using namespace std; 5 int tn,ch[111][26],fail[111],flag[111]; 6 void insert(char *s,int k){ 7 int x=0; 8 for(int i=0; s[i]; ++i){ 9 int y=s[i]-'a'; 10 if(ch[x][y]==0) ch[x][y]=++tn; 11 x=ch[x][y]; 12 } 13 flag[x]|=1<<k; 14 } 15 void init(){ 16 memset(fail,0,sizeof(fail)); 17 queue<int> que; 18 for(int i=0; i<26; ++i){ 19 if(ch[0][i]) que.push(ch[0][i]); 20 } 21 while(!que.empty()){ 22 int x=que.front(); que.pop(); 23 for(int i=0; i<26; ++i){ 24 if(ch[x][i]) que.push(ch[x][i]),fail[ch[x][i]]=ch[fail[x]][i],flag[ch[x][i]]|=flag[ch[fail[x]][i]]; 25 else ch[x][i]=ch[fail[x]][i]; 26 } 27 } 28 } 29 int getCnt(int s){ 30 int res=0; 31 for(int i=0; i<10; ++i){ 32 if((s>>i)&1) ++res; 33 } 34 return res; 35 } 36 int d[26][111][1<<10]; 37 int main(){ 38 char str[11]; 39 int n,m,k; 40 while(~scanf("%d%d%d",&n,&m,&k) && (n||m||k)){ 41 tn=0; 42 memset(ch,0,sizeof(ch)); 43 memset(flag,0,sizeof(flag)); 44 for(int i=0; i<m; ++i){ 45 scanf("%s",str); 46 insert(str,i); 47 } 48 init(); 49 memset(d,0,sizeof(d)); 50 d[0][0][0]=1; 51 for(int i=0; i<n; ++i){ 52 for(int j=0; j<=tn; ++j){ 53 for(int k=0; k<(1<<m); ++k){ 54 if(d[i][j][k]==0) continue; 55 for(int y=0; y<26; ++y){ 56 d[i+1][ch[j][y]][k|flag[ch[j][y]]]+=d[i][j][k]; 57 d[i+1][ch[j][y]][k|flag[ch[j][y]]]%=20090717; 58 } 59 } 60 } 61 } 62 int res=0; 63 for(int i=0; i<=tn; ++i){ 64 for(int j=0; j<(1<<m); ++j){ 65 if(getCnt(j)<k) continue; 66 res+=d[n][i][j]; 67 res%=20090717; 68 } 69 } 70 printf("%d\n",res); 71 } 72 return 0; 73 }