hdu2825(状压dp+AC自动机)

题意:有m个模式串,问长度为n包含至少k中模式串(可重叠)的串有多少种,答案取模。

思路:状压dp,先说下dp数组含义,dp[i][j][k]代表长度为i的字符串状态为j,含有k种模式串。

      由于可以重叠,直接AC自动机构造nxt数组后(同时操作模式串包含其他为其后缀的模式串个数),dp是分n次添加字符,每次枚举Trie树上的节点,枚举状态,枚举添加字符,根据合法状态推向合法状态的状压dp,枚举所添加字符后的操作,因为是可重叠,爬数即可。最后统计所有长度为n,含有模式串种类数>k的状态数量即可。

下面上代码

#include<cstdio>
#include<cstring>
#include<queue>
#define ll long long
using namespace std;
const int mod = 20090717;
const int maxn = 110,N=26;
int dp[N][110][1<<10],num[1<<10];
struct Trie{
    int nxt[maxn][N],fail[maxn],end[maxn];
    int root,tot;
    int newnode(){
        for(int i=0;i<N;i++)
            nxt[tot][i]=-1;
        end[tot++]=0;
        return tot-1;
    }
    void init(){
        tot=0;
        root=newnode();
    }
    void insert(char buf[],int id){
        int len=strlen(buf);
        int now=root;
        for(int i=0;i<len;i++){
            int k=buf[i]-'a';
            if(nxt[now][k]==-1)
                nxt[now][k]=newnode();
            now=nxt[now][k];
        }
        end[now]|=(1<<id);
    }
    void build(){
        queue<int>que;
        fail[root]=root;
        for(int i=0;i<N;i++)
            if(nxt[root][i]==-1) nxt[root][i]=root;
            else{
                fail[nxt[root][i]]=root;
                que.push(nxt[root][i]);
            }
        while(!que.empty()){
            int now=que.front();
            que.pop();
            end[now]|=end[fail[now]];
            for(int i=0;i<N;i++){
                if(nxt[now][i]==-1) nxt[now][i]=nxt[fail[now]][i];
                else{
                    fail[nxt[now][i]]=nxt[fail[now]][i];
                    que.push(nxt[now][i]);
                }
            }
        }
    }
    int solve(int n,int m,int k){
        for(int i=0;i<=n;i++)
            for(int j=0;j<=tot;j++)
                for(int q=0;q<=(1<<m);q++)
                    dp[i][j][q]=0;
        dp[0][0][0]=1;
        for(int i=0;i<n;i++)
            //枚举字符串长度为i,其实就是分n次,每次填上一个字母的方式拓展状态
            for(int j=0;j<tot;j++)//枚举Trie树上的节点
                for(int q=0;q<(1<<m);q++)//枚举一种状态
                if(dp[i][j][q]>0){
            //如果存在即可由此推其他状态,另存在就说明合法(我们只由合法推向合法)
                    for(int x=0;x<N;x++){//在此字符串后添加一个字母
                        int newi=i+1;
                        int newj=nxt[j][x];//因为可以重叠,所以我们爬树即可
                        int newq=(q|end[newj]);
            //如果连重叠都没有可能性存在该字符串则newj回到了根节点,end[newj]则为0
            //即使片尾可能不是完整字符串,这里面也只存着那些完整字符串的值,只要满足[k]即可
                        dp[newi][newj][newq]+=dp[i][j][q];
                        dp[newi][newj][newq]%=mod;
                    }
                }
        int ans=0;
        for(int p=0;p<(1<<m);p++){
            if(num[p]<k) continue;//包含字符串小于k种不计
            for(int i=0;i<tot;i++){
                ans=(ans+dp[n][i][p])%mod;
            }
        }
        return ans;
    }
};
char buf[20];
Trie ac;
int main()
{
    int n,m,k;
    for(int i=0;i<(1<<10);i++){
        num[i]=0;
        for(int j=0;j<10;j++)
            if(i&(1<<j)) num[i]++;
    }
    while(scanf("%d%d%d",&n,&m,&k),n||m||k){
        ac.init();
        for(int i=0;i<m;i++){
            scanf("%s",buf);
            ac.insert(buf,i);
        }
        ac.build();
        printf("%d\n",ac.solve(n,m,k));
    }
}

好吧这确实是kuangbin的代码,我只是加了些注释。

类型标记转载好了,希望不会被喷。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值