【HDU 4945】 2048 动态规划组合数学

【HDU 4945】 2048 动态规划

题意:

给出 n n n个数字,满足 0 ≤ a i ≤ 2048 0\leq a_i\leq 2048 0ai2048。规定新的2048游戏规则,对于一个序列,每次可从序列中选取两个大小相同的数字,消除它们,然后添加一个大小为两数字之和的新的数字。如果通过这种方式能够获得2048,那么就称这个序列为完美数列。

现在需要统计这 n n n个数字的所有子序列中的完美序列个数。答案对998244353取模。(注意子序列的定义,和字串有区别)

n ≤ 1 × 1 0 5 n\leq1\times10^{5} n1×105


值得我写博客的,要么是自己做出来的好题,要么就是背锅题……

这次是后者。

可能这篇题解的作者脑回路有点憨,下面的“元素”、“数字”都是指同一个东西。因为这道题目中序列的元素就是这些数字。

参考思路:

首先,这个题目想要诱导我们去思考2048的游戏规则……我们必须甩开这点重新思考。

  • 首先,能够用来合成2048的数字必须是2的整数次幂。证明略。

  • 其次,对于一些2的整数次幂(幂次不超过11),只要它们的和大于等于2048,那么就一定能够合成2048。这是个充要条件,也就是说,一些2的整数次幂(幂次不超过11)能够合成2048,那么它们的和一定大于等于2048.

注意,题目的要求,不是恰好合成2048,而是能够合成2048

比赛的时候正面去dp,硬是没搞出来。

赛后经过学长的指点,选择了正确的方法。

首先,我们把所有2的整数幂次的数字提取出来,并且按照幂次分类计数。其他的数字不能用于合成2048,但是却可以用于构造子序列,那么如果其他数字有 k k k个,最后对答案的贡献就是 2 k 2^k 2k倍,最后乘上去就行了。

好的,现在看到2的整数次幂的计数结果。一共只有12种数字,我们只需要知道每种数字的个数 c n t 2 i cnt_{2^i} cnt2i。因为在这道题中,子序列中元素的顺序是与题目无关的。

我们知道,对于这12种数字的所有组合方案(注意,同种数字的不同个体要视为不同的元素,因为它们在原来的序列中的下标是不同的),只要组合起来的和大于2048,就可以计入答案,否则就不能计入答案。

我们正难则反,考虑统计有多少种组合的元素和小于2048,最后再用组合总数 2 n − k 2^{n-k} 2nk减去就可以了。

定义 d p ( i , j ) dp(i,j) dp(i,j)表示考虑了 2 0 2^0 20- 2 i 2^i 2i这些种类的元素,选取元素总和为 j j j的方案数。

转移方程 d p ( i , j ) = ∑ k = 0 k × 2 i ≤ j C c n t 2 i k d p ( i − 1 , j − k × 2 i ) dp(i,j)=\sum\limits_{k=0}^{k\times2^i\leq j}\bold C_{cnt_{2^i}}^{k}dp(i-1,j-k\times2^i) dp(i,j)=k=0k×2ijCcnt2ikdp(i1,jk×2i)

令2的整数次幂的数字个数为m,那么答案就是:
2 n − m ( 2 m − ∑ i = 0 2047 d p ( 11 , i ) ) 2^{n-m}(2^m-\sum\limits_{i=0}^{2047}dp(11,i)) 2nm(2mi=02047dp(11,i))

参考代码(和上面有些细节出入,但是原理相同)

#define George_Plover
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <iostream>
#include <iomanip>
#include <algorithm>
#define MAXN 200001
#define MAXM 16000001
#define MOD 998244353
#define LL long long
#define RG register
using namespace std;
int n,m,Case;
int mi[]={1,2,4,8,16,32,64,128,256,512,1024,2048};
int a[MAXN];
int dp[20][3000];
int cnt[3000];
int fac[MAXN];
int inv_fac[MAXN];
int s[3000];

int qpow(int x,int y)
{
    x%=MOD;
    int ret=1;
    while(y)
    {
        if(y&1)
            ret=1ll*ret*x%MOD;
        x=1ll*x*x%MOD;
        y>>=1;
    }
    return ret;
}
int C(int x,int y)
{
    if(!y)return 1;
    return 1ll*fac[x]*inv_fac[y]%MOD*inv_fac[x-y]%MOD;
}
int main()
{
    fac[0]=1;
    for(int i=1;i<MAXN;i++)
        fac[i]=1ll*fac[i-1]*i%MOD;

    inv_fac[MAXN-1]=qpow(fac[MAXN-1],MOD-2);
    
    for(int i=MAXN-2;i>=0;i--)
        inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%MOD;
    
    while(scanf("%d",&n)&&n)
    {
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=11;i++)
            cnt[mi[i]]=0;
        
        int sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            bool flag=0;
            for(int j=0;j<12;j++)
            {
                if(a[i]==mi[j])
                {
                    cnt[a[i]]++;
                    flag=1;
                }
            }
            if(!flag)
                sum++;
        }
        
        for(int i=0;i<=min(cnt[1],2048);i++)
            dp[0][i]=C(cnt[1],i);
        
        for(int i=1;i<12;i++)
        {
            for(int j=0;j<=2048;j++)
            {
                for(int k=0;k*mi[i]+j<=2048&&k<=cnt[mi[i]];k++)
                {
                    dp[i][k*mi[i]+j]+=1ll*dp[i-1][j]*C(cnt[mi[i]],k)%MOD;
                    dp[i][k*mi[i]+j]%=MOD;
                }
            }
        }
        int ans=qpow(2,n-sum);
        for(int i=0;i<2048;i++)
            ans=(ans-dp[11][i])%MOD;
        printf("Case #%d: %lld\n",++Case,(1ll*ans*qpow(2,sum)%MOD+MOD)%MOD);
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值