hdu 2825 Wireless Password

Wireless Password


AC 自动机+ DP
题目意思:给定M个子串,现需要构造一个长度为N的串使得该串中至少包含K个子串,求有多少中构造方法。开始的时候错误的理解题目意思:认为只需要包含k个字串(允许重复)。。。现在状态就很容易写了。dp[i][j][k] 长度为i处于j状态的包含的字串的情况为k的可能串的个数, 方程就可以直接得到了,这里就不写了,参看程序。。


/*

    author      : csuchenan
    algorithm   : AC自动机+DP
    2012-11-01 13:41:39	Accepted	2825	390MS	6764K	3172 B	C++	csu_chenan
*/
#include <cstdio>
#include <cstring>
#include <queue>
using std::queue;
const int maxn = 105;
const int MAX_SIZE = 26;
const int mod = 20090717;

struct Trie{
    int next[MAX_SIZE];
    int fail, tag;
    void init(){
        memset(next, 0, sizeof(next));
        fail = 0;
        tag = 0;
    }

}trie[maxn];
int cnt, n, m, k, r;
int dp[30][maxn][1030];
int map[1030];

void insert(char * str, int root, int s){
    int i;
    while(*str){
        i = (*str ++) - 'a';
        if(trie[root].next[i]==0){
            trie[++cnt].init();
            trie[root].next[i] = cnt;
        }
        root = trie[root].next[i];
    }
    trie[root].tag |= (1<<s);
}

void build_ac(int root){
    queue<int> Q;
    Q.push(root);
    trie[root].fail = root;
    while(!Q.empty()){
        int cur = Q.front();
        Q.pop();
        for(int i = 0; i < MAX_SIZE; i ++){
            int child = trie[cur].next[i];
            if(child!=0){
                Q.push(child);
                if(cur == root)
                    trie[child].fail = root;
                else{
                    int tmp = trie[cur].fail;
                    trie[child].fail = trie[tmp].next[i];
                    trie[child].tag |= trie[ trie[tmp].next[i] ].tag;
                }
            }
            else{
                if(cur==root){
                    trie[cur].next[i] = root;
                }
                else{
                    int tmp = trie[cur].fail;
                    trie[cur].next[i] = trie[tmp].next[i];
                }
            }
        }
    }
}

int main(){

    for(int i = 0; i <= 1024; i ++){
        map[i] = map[i>>1] + (i&1);
    }

    while(scanf("%d%d%d", &n, &m, &k)!=EOF, n||m||k){
        char str[20];
        r = cnt = 0;
        trie[r].init();
        for(int i = 0; i < m; i ++){
            scanf("%s", str);
            insert(str, r, i);
        }
        build_ac(r);
        int mx = 1<<m;
        //这里最好不要使用memset,时间会慢很多,并且跑出来的内存大很多
        //直接数组赋值,速度快了将近一倍,内存也少了很多
        for(int i = 0; i <= n; i ++){
            for(int j = 0; j <= cnt; j ++){
                for(int t = 0; t < mx; t ++)
                    dp[i][j][t] = 0;
            }
        }
        dp[0][0][0] = 1;
        for(int i = 0; i < n; i ++){
            for(int j = 0; j <= cnt; j ++){
                for(int s = 0; s < mx; s ++){
                    if(dp[i][j][s]==0)
                        continue;
                    for(int p = 0; p < MAX_SIZE; p ++){
                        //这个地方要判断当前状态s是否合法即,当从j状态走过的时候,trie[j].tag
                        //是否被包含。。
                        if((s&trie[j].tag) != trie[j].tag){
                            continue;
                        }
                        int cur = trie[j].next[p];
                        int tag = s | trie[cur].tag;
                        dp[i+1][cur][tag] = (dp[i+1][cur][tag] + dp[i][j][s])%mod;
                    }
                }
            }
        }
        int ans = 0;
        for(int i = 0; i <= cnt; i ++){
            for(int j = 0; j < mx; j ++){
                if(map[j] >= k){
                    ans = (ans + dp[n][i][j])%mod;
                }
            }
        }
        printf("%d\n", ans%mod);
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值