HDU 2825 Wireless Password AC自动机+状压DP

题意:
给出密码的长度n,可能含有密码字串的个数m和密码至少含有密码字串的个数k,求有多少种情况。
分析:
因为这个题不是问的密码字串必须全部包含,所以不能矩阵加速= =
果然n的大小变得很小只有25
可以用状压DP来做,具体是每个AC自动机内的节点都编个号,然后getfail的时候像以前矩阵加速getfail一样,假设当前节点的编号是2^k,当前节点的fail指向的点的编号是2^j,那么就把当前节点的编号更新成2^k+2^j(以前矩阵加速是fail节点不能选当前节点也不能选)。
然后dp[i][j][k]表示走到密码的第i位,在AC自动机上匹配到第j个点,状态为k的方案数。
dp[i][j][k]就可以推向dp[i+1][jj][k|cnt[jj]](cnt数组存放节点编号)。
竟然1A了,爽啊。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int a,b,c,mod=20090717;
char s[30];
struct ACautomata{
    int next[255][26],fail[255],cnt[255],dp[26][255][1<<10],bitcnt[1<<10],num,root;
    int newnode()
    {
        memset(next[num],0,sizeof next[num]);
        cnt[num]=0;
        return num++;
    }
    void init()
    {
        num=0;
        root=newnode();
        bitcnt[0]=0;
        for(int i=1;i<(1<<10);++i)
            bitcnt[i]=bitcnt[i>>1]+(i&1);
    }
    void insert(char *s,int l)
    {
        int len=strlen(s),cur=root;
        for(int i=0;i<len;++i)
        {
            int &tmp=next[cur][s[i]-'a'];
            if(!tmp)tmp=newnode();
            cur=tmp;
        }
        cnt[cur]=l;
    }
    void getfail()
    {
        queue<int>q;
        fail[root]=root;
        for(int i=0;i<26;++i)
        {
            int u=next[root][i];
            if(u)
            {
                fail[u]=0;
                q.push(u);
            }
            else next[root][i]=0;
        }
        while(!q.empty())
        {
            int cur=q.front();
            q.pop();
            for(int i=0;i<26;++i)
            {
                int u=next[cur][i];
                if(u)
                {
                    fail[u]=next[fail[cur]][i];
                    cnt[u]|=cnt[fail[u]];
                    q.push(u);
                }
                else next[cur][i]=next[fail[cur]][i];
            }
        }
    }
    int work(int n,int m,int k)
    {
        for(int i=0;i<=n;++i)
            for(int j=0;j<num;++j)
                for(int p=0;p<(1<<m);++p)
                    dp[i][j][p]=0;
        dp[0][0][0]=1;
        for(int i=0;i<n;++i)
            for(int j=0;j<num;++j)
                for(int p=0;p<(1<<m);++p)
                {
                    if(dp[i][j][p]==0)continue;
                    for(int l=0;l<26;++l)
                    {
                        int u=next[j][l];
                        (dp[i+1][u][p|cnt[u]]+=dp[i][j][p])%=mod;
                    }
                }
        int res=0;
        for(int i=0;i<num;++i)
            for(int j=0;j<(1<<m);++j)
                if(bitcnt[j]>=k)(res+=dp[n][i][j])%=mod;
        return res;
    }
}ac;
int main()
{
    while(~scanf("%d%d%d",&a,&b,&c)&&(a+b+c))
    {
        ac.init();
        for(int i=0;i<b;++i)
            scanf("%s",s),ac.insert(s,1<<i);
        ac.getfail();
        printf("%d\n",ac.work(a,b,c));
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值