题目大意:
有N枚硬币,给出每枚硬币的价值,现在要用这些硬币去支付价值为M的东西。
问是否可以找见这样的方案,使得选择用来支付的硬币价值之和恰好为M,如果不存在,输出NoSolution。
如果存在,从小到大输出选择用来支付硬币的价值,如果有多种方案,则输出字典序最小的那个。
思路:
题目中的价值c[i]与质量w[i]是等价的,可以把c[i]与w[i]使用一个数组存放。
由于题目要求以价值从小到大的顺序输出,因此需要先把数组从小到大排序,然后再进行正常的01背包的dp操作。
状态转移方程dp[i][v]=max{dp[i-1][v],dp[i-1][v-w[i]]+c[i]}.
在此基础上开设一个二维数组choice[i-1][v],用来计算dp[i][v]时是选择了哪个策略。
如果状态转移时选择了dp[i-1][v]则choice[i][v]=0,表示不放第i件物品。
如果在状态转移时选择了dp[i-1][v-w[i]]+c[i],则choice[i][v]=1,表示选择了第i件物品。
这样dp数组求解完之后,选择最大的dp[n][v],从第n件物品开始倒着查看每一件物品是否放入背包。
无解的条件为dp[n][m]!=m,因为题目要求恰好能付清价值为M的货币,因此只要dp[m]不是m就说明无解。
求解dp数组时,如果两个策略的大小相等,则应该选择放第i件物品的策略。
AC代码:
//PAT_A 1068
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 10010;
const int maxv = 110;
int w[maxn], dp[maxn][maxv] = { 0 };
bool choice[maxn][maxv], flag[maxn];
bool cmp(int a, int b) {
return a > b;
}
int main() {
int n, m;
(void)scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
(void)scanf("%d", &w[i]);
}
sort(w + 1, w + n + 1, cmp);
for (int i = 1; i <= n; i++) {
for (int v = w[i]; v <= m; v++) {
int temp1 = dp[i - 1][v - w[i]] + w[i];//放
int temp2 = dp[i - 1][v];//不放
if (temp1 >= temp2) {//保证字典序,如果相等也要选
choice[i][v] = 1;//选i号硬币
dp[i][v] = temp1;
}
else {
choice[i][v] = 0;//不选i号硬币
dp[i][v] = temp2;
}
}
}
if (dp[n][m] != m)printf("No Solution");
else {
int k = n, num = 0, v = m;//看choice数组的第v列
while (k >= 0) {
if (choice[k][v] == 1) {
flag[k] = true;
v -= w[k];
num++;
}
else flag[k] = false;
k--;
}
for (int i = n; i >= 1; i--) {
if (flag[i] == true) {
printf("%d", w[i]);
num--;
if (num > 0)printf(" ");
}
}
}
return 0;
}