HDU 2825(AC自动机+状压dp)

传送门

题面:

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

题意:

    给 m 个单词构成的集合,统计所有长度为 n 的串中,包含至少 k 个单词的方案数。

题目分析:

    AC自动机好题,bzoj1010的加强版。

    在bzoj1010中,我们要求的是包含至少1个单词的方案数,当时我们的做法是用一个dp去求出不符合条件的答案,并用总方案数-不符合条件的方案数。

    但是,在这个题中,倘若我们直接采取bzoj1010的方法去做的话,我们发现,至少包含k个单词的方案数并没有像1个方案数那么好求。因此我们需要进一步分析。考虑到一共最多会有m个单词,即最多一共会有2^m中不同的单词的搭配,因此我们考虑在普通dp的基础上增加一维作为状态压缩。

    我们设为:长度为i的字符串,当前位于Trie树上的第j个结点,且当时单词选取的状态的二进制数为k的符合的方案数。故此时不难发现,该状态必定是由前一个结点位置转移过来的,故会有状态转移方程:

    最后求出即为答案。

代码:

#include <bits/stdc++.h>
#define maxn 300
using namespace std;
int dp[30][maxn][1<<10];
int num[maxn*10];
char st[20];
int n,m,k;
const int mod=20090717;
struct Trie{//AC自动机模板
    int next[maxn][26],fail[maxn],root,id,End[maxn];
    int newnode(){
        for(int i=0;i<26;i++){
            next[id][i]=-1;
        }
        End[id]=0;
        return id++;
    }
    void init(){
        id=0;
        root=newnode();
    }
    void Insert(char *str,int index){
        int len=strlen(str);
        int now=root;
        for(int i=0;i<len;i++){
            if(next[now][str[i]-'a']==-1){
                next[now][str[i]-'a']=newnode();
            }
            now=next[now][str[i]-'a'];
        }
        End[now]|=(1<<index);
    }
    void build(){
        queue<int>que;
        for(int i=0;i<26;i++){
            if(next[root][i]==-1){
                next[root][i]=root;
            }
            else{
                fail[next[root][i]]=root;
                que.push(next[root][i]);
            }
        }
        while(!que.empty()){
            int now=que.front();
            que.pop();
            End[now]|=End[fail[now]];
            for(int i=0;i<26;i++){
                if(next[now][i]==-1){
                    next[now][i]=next[fail[now]][i];
                }
                else{
                    fail[next[now][i]]=next[fail[now]][i];
                    que.push(next[now][i]);
                }
            }
        }
    }
}ac;
void get_num(){//获取每个数中的1的个数
    for(int i=0;i<(1<<10);i++){
        num[i]=0;
        for(int j=0;j<10;j++){
            if(i&(1<<j)){
                num[i]++;
            }
        }
    }
}
void solve(){
    for(int i=0;i<=n;i++){
        for(int j=0;j<ac.id;j++){
            for(int t=0;t<(1<<m);t++) dp[i][j][t]=0;
        }
    }
    dp[0][0][0]=1;
    for(int i=1;i<=n;i++){//dp状态转移
        for(int j=0;j<ac.id;j++){
            for(int l=0;l<(1<<m);l++){
                if(!dp[i-1][j][l]) continue;
                for(int x=0;x<26;x++){
                    int nextj=ac.next[j][x];
                    int tmp=(l|ac.End[nextj]);
                    dp[i][ac.next[j][x]][tmp]+=dp[i-1][j][l];
                    dp[i][ac.next[j][x]][tmp]%=mod;
                }
            }
        }
    }
    int res=0;
    for(int i=0;i<(1<<m);i++){
        if(num[i]<k) continue;
        for(int j=0;j<ac.id;j++){
            res=(res+dp[n][j][i])%mod;
        }
    }
    printf("%d\n",res);
}
int main()
{
    get_num();
    while(~scanf("%d%d%d",&n,&m,&k)){
        if(n==0&&m==0&&k==0) break;
        ac.init();
        for(int i=0;i<m;i++){
            scanf("%s",st);
            ac.Insert(st,i);
        }
        ac.build();
        solve();
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值