1068. Find More Coins (30)-PAT甲级真题(01背包,2种解法)
1068. Find More Coins (30)-PAT甲级真题(01背包)
Eva loves to collect coins from all over the universe, including some other planets like Mars. One day she visited a universal shopping mall which could accept all kinds of coins as payments. However, there was a special requirement of the payment: for each bill, she must pay the exact amount. Since she has as many as 104 coins with her, she definitely needs your help. You are supposed to tell her, for any given amount of money, whether or not she can find some coins to pay for it.
Input Specification:
Each input file contains one test case. For each case, the first line contains 2 positive numbers: N (<=104, the total number of coins) and M(<=102, the amount of money Eva has to pay). The second line contains N face values of the coins, which are all positive numbers. All the numbers in a line are separated by a space.
Output Specification:
For each test case, print in one line the face values V1 <= V2 <= … <= Vk such that V1 + V2 + … + Vk = M. All the numbers must be separated by a space, and there must be no extra space at the end of the line. If such a solution is not unique, output the smallest sequence. If there is no solution, output “No Solution” instead.
Note: sequence {A[1], A[2], …} is said to be “smaller” than sequence {B[1], B[2], …} if there exists k >= 1 such that A[i]=B[i] for all i < k, and A[k] < B[k].
Sample Input 1:
8 9
5 9 8 7 2 3 4 1
Sample Output 1:
1 3 5
Sample Input 2:
4 8
7 2 4 3
Sample Output 2:
No Solution
题目大意:用n个硬币买价值为m的东西,输出使用方案,使得正好几个硬币加起来价值为m。从小到大排列,输出最小的那个排列方案
分析:
两个难点:
- 判断是否有正好几个硬币加起来价值为m
- 如果有的话,如何输出最小的那个排列方案
判断是否有正好几个硬币加起来价值为m,就是一个01背包问题,是否选取第i个物品。
定义状态:dp[i][j]为前i个物品中,选取金额为 j 的选法是否存在。
状态转移方程:
- 选取当前的金额:
dp[i][j] = dp[i-1][j-a[i]],如果dp[i-1][j-a[i]]为true,则dp[i][j]为true - 不选取当前的金额:
dp[i][j] = dp[i-1][j]
最后判断dp[n][m]是否为true即可
那到第二个问题了:如果有的话,如何输出最小的那个排列方案?
因为排列硬币的顺序不是按给的顺序,可以任意排列已有的金额
为了保证排列方案最小,就要保证序列前面的金额要小;
要保证序列前面的金额最小,就保证后面拿的金额是最大即可
我们可以把输入序列从大到小进行排列,然后就得从后往前判断每一个物品是否存在于答案序列中,如果存在就输出该值即可。
回溯逻辑:若f[n][m] = true(存在解),则从最后一个硬币(n)开始向前遍历:
对每个硬币a[k],检查 “若选择它,剩余金额m - a[k]能否由前k-1个硬币组成”(即m >= a[k]且f[k-1][m - a[k]] = true)。若满足,则选择该硬币,更新剩余金额m -= a[k],继续向前遍历直至m = 0。
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N = 10010, M = 110;
int a[N];
bool dp[N][M];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
//从大到小排序
sort(a + 1, a + n + 1, greater<int>());
//动态规划计算
dp[0][0] = true;
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++)
{
dp[i][j] = dp[i - 1][j];
if (j >= a[i]) dp[i][j] |= dp[i - 1][j - a[i]];
}
//输出答案序列
if (!dp[n][m]) printf("No Solution");
else
{
bool is_first = true;
while (n)
{
if (m >= a[n] && dp[n - 1][m - a[n]])
{
if (is_first) is_first = false;
else cout << " ";
cout << a[n];
m -= a[n];
}
n--;
}
}
return 0;
}
在写完之后,搜索了其他的解法:
dp状态定义:dp[j] 表示 “在前 i 个硬币中进行选择时,能够组成的不超过金额 j 的最大总金额”。
最后根据dp[m] 是否等于 m判断是否存在解,若存在解,则根据choice[10010][110]来选择序列
两种解法大体相同,只是有细枝末节的差别
差别:
- dp的定义不同,一个一维数组,一个二维数组,这导致了一维数组在计算dp时需要从m递减,防止覆盖前i-1的结果
- 输出序列的方式不同,其实差不多
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int dp[10010], w[10010];
bool choice[10010][110];
int cmp1(int a, int b){return a > b;}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%d", &w[i]);
sort(w + 1, w + n + 1, cmp1);
for(int i = 1; i <= n; i++) {
for(int j = m; j >= w[i]; j--) {
if(dp[j] <= dp[j-w[i]] + w[i]) {
choice[i][j] = true;
dp[j] = dp[j-w[i]] + w[i];
}
}
}
if(dp[m] != m) printf("No Solution");
else {
vector<int> arr;
int v = m, index = n;
while(v > 0) {
if(choice[index][v] == true) {
arr.push_back(w[index]);
v -= w[index];
}
index--;
}
for(int i = 0; i < arr.size(); i++) {
if(i != 0) printf(" ");
printf("%d", arr[i]);
}
}
return 0;
}

浙公网安备 33010602011771号