PAT-A,1068,题目地址:https://www.patest.cn/contests/pat-a-practise/1068
这是一道01背包问题,解题时注意题意的转化:
- 可以将每个coin都看成value和weight都相同的物品
- 要求所付的钱刚刚好,相当于要求背包必须刚好塞满,且价值最大。(限制背包体积相当于限制coin的总和不能超过所要付的钱,在此条件下求coin组合的最大值,如果这个最大值刚好等于要付的钱,则有解,此时背包也刚好处于塞满状态,否则无解)
- 最后要求从小到大输出coin的组合,且有多解时输出最小的组合。这是此题的难点所在,我们应该将coin从大到小排序,在放进背包时也从大到小逐个检查物品,更新背包价值的条件是在加入一个新的物品后,价值>=原价值,注意此时等号的意义,由于物品是从大到小排序的,如果一个新的物品的加入可以保证价值和原来相同,则此时一定是发现了更小的组合。
代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
int bags[101];
bool selected[10001][101];
bool cmp(const int &a, const int &b){
return a > b;
}
int main(){
int coins[10001];
int n, m;// n coins, pay m
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> coins[i];
}
sort(coins + 1, coins + 1 + n, cmp);
for(int i = 1; i <= n; i++){
for(int j = m; j >= 1; j--){ //之所以要反着来,和背包问题的更新规则有关
if(j - coins[i] >= 0 && bags[j-coins[i]] + coins[i] >= bags[j]){ //等号必须取到,否则输出的解是最大的sequence
selected[i][j] = true; //跟踪哪个物品被选择了
bags[j] = bags[j-coins[i]] + coins[i];
}
}
}
if(bags[m] != m){
cout << "No Solution" << endl;
return 0;
}
//下面是输出最优组合的过程,其实和背包问题的更新规则有关,就是沿着选出解的路径,反着走回去,就找到了所有被选择的数字。
int j = m, i = n;
while(1){
if(selected[i][j] == true){
cout << coins[i];
j -= coins[i];
if(j != 0)
cout << " ";
}
i--;
if(j == 0 || i == 0)
break;
}
cout << endl;
return 0;
}