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