拼凑硬币
时间限制:(每个case)2s 空间限制:128MB
小Q十分富有,拥有非常多的硬币,小Q拥有的硬币是有规律的,对于所有的非负整数K,小Q恰好各有两个面值为2^K的硬币,所以小Q拥有的硬币就是1,1,2,2,4,4,8,8,…。小Q有一天去商店购买东西需要支付n元钱,小Q想知道有多少种方案从他拥有的硬币中选取一些拼凑起来恰好是n元(如果两种方案某个面值的硬币选取的个数不一样就考虑为不一样的方案)。
输入:
输入包括一个整数n(1<=n<=10^18),表示小Q需要支付多少钱。注意n的范围。
输出:
输出一个整数,表示小Q可以拼凑出n元钱放的方案数。
样例输入:6
样例输出:3
这道题可以用动态规划来做,只是状态转移方程比较难想,动态规划是一个填表的过程,首先需要确定表是1维数组还是二维数组,尝试1维数组基本上不可行,直接从二维表入手,创建二维数组res
,其中行数为n+1,列数为硬币的种数(不超过
l
o
g
2
(
n
)
log_2(n)
log2(n)),res[i][j]
表示使用j
种硬币组合出i
元的方案数,由于每种硬币可以使用0、1、2次,假设使用m
次,则剩余rest = i - (2^j) * m
元,若rest>=0
则只需要知道使用前j-1
种硬币组合出rest
的方案数,即res[rest][j-1]
,若rest<0
,则表明不能使用m
次,因为会超出i
元。因此可以得到状态转移方程为:
r
e
s
[
i
]
[
j
]
=
r
e
s
[
i
−
2
j
∗
0
]
[
j
−
1
]
+
r
e
s
[
i
−
2
j
∗
1
]
[
j
−
1
]
+
r
e
s
[
i
−
2
j
∗
2
]
[
j
−
1
]
res[i][j] = res[i-2^j*0][j-1] + res[i-2^j*1][j-1] + res[i-2^j*2][j-1]
res[i][j]=res[i−2j∗0][j−1]+res[i−2j∗1][j−1]+res[i−2j∗2][j−1]
在实现时注意特殊情况,res[0,j] = 1,res[1,j] = 1,以及res[2,j] = 1(使用两个1元硬币可以组合出2,因此方案数为1)
public class main {
public static int n;
public static int solve() {
int coinNum = (int) (Math.log(n) / Math.log(2)) + 1; // 硬币1也占一个
int res[][] = new int[n + 1][coinNum];
// 当n=0时,所有硬币都用不到,因此方案数为1
// 当n=1时,只会用到面值为1的硬币,其它硬币都不用,因此方案数也为1
for (int j = 0; j < coinNum; j++) {
res[0][j] = 1;
res[1][j] = 1;
}
// 动态规划的二维数组含义 res[i][j]: 只用前j种硬币组合出i元的方案数
// 状态转移方程:res[i][j] = res[i-2^j*0][j-1] + res[i-2^j*1][j-1] + res[i-2^j*2][j-1]
// 因为每种硬币可以被使用0、1、2次,三种情况对应的剩余元数即i-2^j*0、i-2^j*1、i-2^j*2
// 为什么是j-1? 因为res[i][j]表示现在才用第j种硬币
for (int i = 2; i < n + 1; i++) {
for (int j = 1; j < coinNum; j++) {
int count = 0;
int coinVal = (int) Math.pow(2, j); // 第j种硬币的面值
// case1: 不使用该硬币
int rest = i - coinVal * 0;
if (rest >= 0)
count += res[rest][j - 1];
// case2: 使用该硬币1次
rest = i - coinVal * 1;
if (rest >= 0)
count += res[rest][j - 1];
// case3: 使用该硬币2次
rest = i - coinVal * 2;
if (rest >= 0)
count += res[rest][j - 1];
res[i][j] = count; // 三种情况下的所有方案和
}
}
return res[n][coinNum - 1];
}
public static void main(String[] args) {
n = 1;
System.out.print(solve());
}
}