hdu 2825 Wireless Password(AC自动机+状压DP)

题意:给出m个串,现在要生成一个长度为n的串,并且这个串至少包含k个给出的串,问生成串的方案数。

思路:dp[i][j][k]表示前长度为i的串,当前状态为j,包含的串为k,k是一个二进制数,表示包含的串。把m个串构建成ac自动机以后就可以dp了。先预处理出每个状态的增加的串的个数num[u],则状态转移方程为dp[i+1][u][k|num[u]]+=dp[i][j][k];其中u为发生转移后的状态。最后把所有数量大于等于k的加起来就行了。最开始dp数组用的memset,交了一发TLE了,又交了一发900ms+过了……后来改成循环,变成500ms+了……

代码:


#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=100+10;
const int mod=20090717;
int ch[maxn][26],next[maxn],lastv[maxn],val[maxn],size;
int n,m,k;
void Init()
{
    memset(ch[0],0,sizeof(ch[0]));
    memset(next,0,sizeof(next));
    memset(lastv,0,sizeof(lastv));
    size=0;val[0]=0;
}
void Insert(const char *s,int v)
{
    int u=0,len=strlen(s);
    for(int i=0;i<len;++i)
    {
        int c=s[i]-'a';
        if(!ch[u][c])
        {
            ch[u][c]=++size;
            memset(ch[size],0,sizeof(ch[size]));
            val[size]=0;
        }
        u=ch[u][c];
    }
    val[u]=v;
}
void build()
{
    queue<int>q;
    for(int c=0;c<26;++c)
        if(ch[0][c]) q.push(ch[0][c]);
    int r,u,v;
    while(!q.empty())
    {
        r=q.front();q.pop();
        for(int c=0;c<26;++c)
        {
            u=ch[r][c];
            if(!u) {ch[r][c]=ch[next[r]][c];continue;}
            q.push(u);
            v=next[r];
            while(v&&!ch[v][c]) v=next[v];
            next[u]=ch[v][c];
            lastv[u]=val[next[u]]?next[u]:lastv[next[u]];
        }
    }
}
int p[1<<10],dp[30][maxn][1<<10],num[maxn];
int solve()
{
    int total=1<<m;
    int u,x;
    memset(p,0,sizeof(p));
    for(int i=0;i<total;++i)
    {
        x=i;
        while(x)
        {
            p[i]++;
            x&=(x-1);
        }
    }
    memset(num,0,sizeof(num));
    for(int j=0;j<=size;++j)
        if(val[j]||lastv[j])
        {
            u=val[j]?j:lastv[j];
            x=0;
            while(u)
            {
                x|=(1<<(val[u]-1));
                u=lastv[u];
            }
            num[j]=x;
        }
    for(int i=0;i<=n;++i)
        for(int j=0;j<=size;++j)
            for(int t=0;t<total;++t)
                dp[i][j][t]=0;
    dp[0][0][0]=1;
    for(int i=0;i<n;++i)
        for(int j=0;j<=size;++j)
            for(int t=0;t<total;++t)
            {
                if(!dp[i][j][t]) continue;
                for(int c=0;c<26;++c)
                {
                    u=ch[j][c];
                    dp[i+1][u][t|num[u]]=(dp[i+1][u][t|num[u]]+dp[i][j][t])%mod;
                }
            }
    int ans=0;
    for(int i=0;i<=size;++i)
        for(int j=0;j<total;++j)
            if(p[j]>=k) ans=(ans+dp[n][i][j])%mod;
    return ans;
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    char str[20];
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        if(n==0&&m==0&&k==0) break;
        Init();
        for(int i=1;i<=m;++i)
        {
            scanf("%s",str);
            Insert(str,i);
        }
        build();
        int ans=solve();
        printf("%d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值