题解 | #换钱的方法数#
详细解释见书
部分注释见下面代码:
#include<bits/stdc++.h>
using namespace std;
size_t modV = (pow(10, 9) + 7);
size_t process1(vector<int> &arr, int aim, int idx) {
//表示所有情况遍历完,aim=0时,才递归返回1,否则为0
if (idx == arr.size())
return aim == 0 ? 1 : 0;
size_t res = 0;
//注意这里是res +,而不是乘;因为一次递归(一个分支)返回值为1或0
for (int i = 0; (aim - arr[idx] * i) >= 0; i++) {
res += process1(arr, aim - arr[idx] * i, idx + 1);
res = res % modV;
}
return res;
}
//暴力递归
//最差情况的时间复杂度:O(aim^N)
size_t coins1(vector<int> &arr, int aim) {
if (arr.empty() || aim < 0)
return -1;
if (aim == 0)
return 0;
return process1(arr, aim, 0);
}
//map[i][j]表示递归过程p[i][j]的返回值
//map[i][j]=0,表示递归过程p[i][j]从未计算过
//map[i][j]=-1,表示递归过程p[i][j]计算过,值为0
size_t process2(vector<int> &arr, int aim, int idx, vector<vector<size_t>> &map) {
size_t res = 0;
if (idx == arr.size())
res = aim == 0 ? 1 : 0;
else {
size_t mapValue = 0;
for (int i = 0; (aim - arr[idx] * i) >= 0; i++) {
mapValue = map[idx + 1][aim - arr[idx] * i];
if (mapValue != 0)
res += (mapValue == -1 ? 0 : mapValue);
else
res += process2(arr, aim - arr[idx] * i, idx + 1, map);
}
}
map[idx][aim] = (res == 0 ? -1 : res);
return res % modV;
}
//暴力递归的初步优化----记忆搜索
//时间复杂度:O(N * aim^2)
size_t coins2(vector<int> &arr, int aim) {
if (arr.empty() || aim < 0)
return -1;
if (aim == 0)
return 0;
vector<vector<size_t>> map(arr.size() + 1, vector<size_t>(aim + 1));
return process2(arr, aim, 0, map);
}
//动态规划方法一
//时间复杂度:O(N * aim^2)
size_t coins3(vector<int> &arr, int aim) {
if (arr.empty() || aim < 0)
return -1;
if (aim == 0)
return 0;
//dp[i][j]表示在使用arr[0...i]货币的情况下,组成货币j的方法数
vector<vector<size_t>> dp(arr.size(), vector<size_t>(aim + 1));
//当j为0时,使用0张货币,方法数全为1
for (int i = 0; i < arr.size(); i++) {
dp[i][0] = 1;
}
//当i=0时,即只使用货币arr[0],可换取的价值为arr[0]的倍数
for (int j = 0; (arr[0] * j) <= aim; j++) {
dp[0][arr[0] * j] = 1;
}
int num = 0;
for (int i = 1; i < arr.size(); i++) {
for (int j = 1; j <= aim; j++) {
num = 0;
//使用k张arr[i],剩下的钱j-arr[i]*k用arr[0...i-1]货币组成
for (int k = 0; j - arr[i] * k >= 0; k++) {
num += dp[i - 1][j - arr[i] * k];
num = num % modV;
}
dp[i][j] = num;
}
}
return dp[arr.size() - 1][aim];
}
//动态规划方法二
//时间复杂度:O(N * aim)
size_t coins4(vector<int> &arr, int aim) {
if (arr.empty() || aim < 0)
return -1;
if (aim == 0)
return 0;
//dp[i][j]表示在使用arr[0...i]货币的情况下,组成货币j的方法数
vector<vector<size_t>> dp(arr.size(), vector<size_t>(aim + 1));
//当j为0时,使用0张货币,方法数全为1
for (int i = 0; i < arr.size(); i++) {
dp[i][0] = 1;
}
//当i=0时,即只使用货币arr[0],可换取的价值为arr[0]的倍数
for (int j = 0; (arr[0] * j) <= aim; j++) {
dp[0][arr[0] * j] = 1;
}
for (int i = 1; i < arr.size(); i++) {
for (int j = 1; j <= aim; j++) {
dp[i][j] = dp[i - 1][j];
dp[i][j] += (j - arr[i] >= 0) ? dp[i][j - arr[i]] : 0;
dp[i][j] = dp[i][j] % modV;
}
}
return dp[arr.size() - 1][aim];
}
//动态规划方法三(空间压缩)
//时间复杂度:O(N * aim)
size_t coins5(vector<int>& arr, int aim) {
if (arr.empty() || aim < 0)
return -1;
if (aim == 0)
return 0;
//dp[i][j]表示在使用arr[0...i]货币的情况下,组成货币j的方法数(初始时j=0,结尾时j=aim)
vector<size_t> dp(aim + 1);
//当i=0时,即只使用货币arr[0],可换取的价值为arr[0]的倍数
for (int j = 0; (arr[0] * j) <= aim; j++) {
dp[arr[0] * j] = 1;
}
for (int i = 1; i < arr.size(); i++) {
for (int j = 1; j <= aim; j++) {
dp[j] += (j - arr[i] >= 0) ? dp[j - arr[i]] : 0;
dp[j] = dp[j] % modV;
}
}
return dp[aim] ;
}
int main() {
int n, aim;
cin >> n >> aim;
vector<int> arr(n);
for (auto i = 0; i < n; i++) {
cin >> arr[i];
}
auto res = coins4(arr, aim);
cout << res << endl;
return 0;
}