hdu 4906 Our happy ending (状态压缩dp)

题意: 

有n个数,每个数x 满足0<=x<=L,如果一列数中选出某些数和为k那么称这个数列为好的数列,

求能构造出这样的数列的个数。n和k最大为20,L最大为10^9。

输出结果要对1000000000+7取模。


算法:

由于k只有20,可以对能凑成的数用二进制整数的一个位来表示,k位置为1表示当前状态能得到

整数k,否则不能。dp[i][s]表示前i个数能得到状态s。

dp[i+1][s|((s<<j)&full)] = sum(dp[i][s] )  ,初始化为dp[1] = 1。

为什么新状态new = s|((s<<j)&full)呢?  比如:前i个数我可以得到sum=1,2,3,如果第i+1个数我取

2,则我能得到1,2,3相应的加2即得到3,4,5。也就是把s<<j。至于&full(全状态)是因为可能移位后

超过我要得到的全状态,记录dp[s]时,s'偏大了,相当于丢失了一部分记录所以结果偏小。


如果初始化为dp[0]=1,那么状态转移则为new = s|(1<<(j-1))|((s<<j)&full).

dp[i+1][new] = sum(dp[i][s])

为什么多了个|(1<<(j-1))呢?因为从dp[0]=1递推到每个sum等于所取的数本身就要从某一位置一开始。


#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 22

using namespace std;

typedef long long ll;
const int mod = 1000000000+7;
int dp[1<<maxn],n,l,k;

void solve()
{
    int lim = min(l,k);
    int full = (1<<k)-1;

    memset(dp,0,sizeof(dp));
    dp[0] = 1;

    while(n--)
    {
        for(int s=full;s>=0;s--)
        {
            if(dp[s]>0)
            {
                ll tmp = dp[s];
                for(int j=1;j<=lim;j++)
                {
                    int next = s|(1<<(j-1))|((s<<j)&full);
                    dp[next] = (dp[next]+tmp)%mod;
                }
                if(lim<l)
                    dp[s] = (dp[s]+(tmp*(l-lim))%mod)%mod;
            }
        }
    }

    ll ans = 0;
    for(int s=0;s<=full;s++)
    {
        if(s&(1<<(k-1)))
            ans= (ans+dp[s])%mod;
    }
    printf("%I64d\n",ans);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&k,&l);
        solve();
    }
    return 0;
}

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn (1<<22)
using namespace std;

typedef long long ll;
const int mod = 1000000000+7;
int dp[maxn],k,l,n;

void solve()
{
    memset(dp,0,sizeof(dp));
    int full = (1<<(k+1))-1;
    int lim = min(l,k);

    dp[1] = 1;
    for(int i=0;i<n;i++)
    {
        for(int s=full;s>=0;s--)
        {
            if(dp[s]>0)
            {
                ll tmp = dp[s];
                for(int j=1;j<=lim;j++)
                    dp[full&(s|(s<<j))] = (dp[full&(s|(s<<j))]+tmp)%mod;
                if(lim<l)
                    dp[s] = (dp[s]+((l-lim)*tmp)%mod)%mod;
            }
        }
    }

    ll ans = 0;
    for(int s=1;s<=full;s++)
    {
        if(s&(1<<k))
            ans = (ans+dp[s])%mod;
    }
    printf("%I64d\n",ans);
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&k,&l);
        solve();
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值