挑战性题目DSCT101:硬币找换问题
问题描述
有一堆数字,1,2,4,8,⋯ ,2n1,2,4,8,\cdots,2^n1,2,4,8,⋯,2n,每个数字各两个。要求选取部分数字,相加凑出一个给定的数字MMM。
题解
假如每个数字只有111个,那么每个正整数MMM自然只有一种表示方式。当每种数字都有222个时,那么2i2^i2i可以被两个2i−12^{i-1}2i−1的数字表示,所以可以通过组合多个面值小的数字来代替面值大的数字。而由于每种数字仅有222个,所以无论如何组合1∼2i−11\sim2^{i-1}1∼2i−1面值的数字,最多只能再凑出一个面值为2i2^i2i的数字。
所以我们使用动态规划中的数位dp算法,使用数组dp[i][j]
来表示考虑到面值为2i2^i2i的数字,且已经用小面值的数字凑出了jjj个2i2^i2i面值的数字的方案数。在转移时,我们枚举选取2i2^i2i数字的个数kkk,当(i+j)%2(i+j)\%2(i+j)%2等于我们需要凑出的面值的第iii个二进制位时,就表示我们完成了这一位的表示,并且可以向前进位了,于是dp[i+1][(j+k)/2]
的方案数就加上dp[i][j]
的方案数。
最后,总的方案数即保存在处理完所有二进制位,并且不进位的情况中,即ans=dp[log_2(M)+1][0]
。
基于此,对于每种数字都有kkk个的情况,仍然可以照此处理,jjj的取值范围为0∼⌊k2i−12i⌋≈k0\sim\left\lfloor k\frac{2^i-1}{2^i}\right\rfloor\approx k0∼⌊k2i2i−1⌋≈k,而枚举M的每一位需要log2M{log}_2{M}log2M的时间,枚举每个数字选择的个数需要k的时间,所以总的时间复杂度为O(k2log2M)O\left(k^2{log}_2{M}\right)O(k2log2M)。
代码
#include<stdio.h>
#include<stdlib.h>
int dp[33][3],n,i,j,k;
int main(int argc,char* argv[])
{
n=atoi(argv[1]);
dp[0][0]=1;
for(i=0;(1ll<<i)<=n;++i)
for(j=0;j<=1;++j)for(k=0;k<=2;++k)
if((j+k&1)==(1&(n>>i)))dp[i+1][j+k>>1]+=dp[i][j];
printf("%d\n",dp[i][0]);
}