AC自动机+状压DP

  HDU 2825

题目大义:

  给定m个长度不超过10的单词,你需要构造一个长度为n的文本串,文本串包含的给定单词不小于k个,求方案数。

分析:

  看完题目觉得像是数学题,需要一些容斥啥的算算(其实是瞎想,不知道怎么实现),最后也只会暴力搜索判断这种没分数的写法。其实是一道DP题,算是一种利用AC自动机做DP的套路,需要学会。

  定义F【i】【j】【mask】,表示当前文本串长度为i,匹配到了AC自动机上的第j个节点,已包含的单词集合为mask时的方案数。显然我们可以从j节点向下更新,继续枚举一个l字母(0<=l<26)来匹配第i+1个字母,那么我们就可以向下递推了,假设当前状态方案数已知,那么可以累加到F【i+1】【tr j,l】【mask|sum【tr j,l】】中。sum表示到当前这个节点已经包含的单词集合,采用二进制。

  注意:sum数组在建立fail指针的同时求出。

#include<bits/stdc++.h>
using namespace std;
const int N=500;
const int mod=20090717;
int n,m,limit,tot,tr[N][30],sum[N],fail[N],f[30][150][1500],cnt[1500];
char s[N];
void init(){
    memset(tr,0,sizeof(tr));
    memset(sum,0,sizeof(sum));
    memset(fail,0,sizeof(fail));
    memset(f,0,sizeof(f));
    tot=0;
    f[0][0][0]=1;
}
void insert(int pos){
    int len=strlen(s+1),now=0;
    for(int i=1;i<=len;++i){
        int ch=s[i]-'a';
        if(!tr[now][ch]) tr[now][ch]=++tot;
        now=tr[now][ch];
    }sum[now]=1<<pos;
}
void build(){
    queue<int>q;
    for(int i=0;i<26;++i){
        if(tr[0][i]) q.push(tr[0][i]),fail[tr[0][i]]=0;
    }
    while(q.size()){
        int x=q.front();q.pop();
        for(int i=0;i<26;++i){
            if(tr[x][i]){
                int v=tr[x][i];
                fail[v]=tr[fail[x]][i];
                q.push(v);
            }else tr[x][i]=tr[fail[x]][i];
            sum[tr[x][i]]|=sum[tr[fail[x]][i]];
        }
    }
}
int count(int x){
    int res=0;
    for(;x;x>>=1){
        if(x&1) res++;
    }
    return res;
}
int main(){
    for(int i=0;i<1<<10;++i) cnt[i]=count(i);
    while(~scanf("%d%d%d",&n,&m,&limit)){
        if(!(n||m||limit)) break;
        init();
        for(int i=0;i<m;++i) scanf("%s",s+1),insert(i);
        build();
        for(int i=0;i<=n;++i){
            for(int j=0;j<=tot;++j){
                for(int k=0;k<1<<m;++k){
                    if(!f[i][j][k]) continue;
                    for(int l=0;l<26;++l){
                        int v=tr[j][l];
                        f[i+1][v][k|sum[v]]=(f[i+1][v][k|sum[v]]+f[i][j][k])%mod;
                    }
                }
            }
        }
        int ans=0;
        for(int i=0;i<=tot;++i) for(int j=0;j<1<<m;++j) if(cnt[j]>=limit) ans=(ans+f[n][i][j])%mod;
        printf("%d\n",ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/kgxw0430/p/10255650.html