题目
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
思路
属于经典0-1背包问题(每个币最多选一次),物品重量和价值均为币值a[i],把问题转化成使用这些币值在总价值m范围内所能组合出的最大价值,若该最大价值=m则有解,若最大价值<m则无解。
关键点是为了求最小序列,可先将币值降序排列,每次找到新的相等的最优解时都采用,这就保证了较小的币值总能被选中。
使用动态规划法。用F[i][j]表示用前i个币在总价值不超过j时能达到的最大价值。若最后F[n][m] = m,则表示有解;若F[n][m] < m,则表示无解。
若选择了第i个币,则当前最大总价值为F[i-1][j-a[i]] + a[i];若不选择第i个币,则当前最大总价值等于F[i-1][j]。
写出递推方程:
F[i][0] = 0
F[0][j] = 0
F[i][j] = max{ F[i-1][j], F[i-1][j-a[i]] + a[i] }
F[i][j]只需保存最新一组值,可使用一维数组F[j]代替,每轮遍历都覆盖上一轮的F[j]。
设置标记函数B[i][j],记录取得最大价值时是否选用了第i个币,可以追溯出解的选取序列。它不能用一维数组取代,因为前面轮次的值还要在追溯过程中使用。
易错点
真正实现代码时还是出现了很多错误。
循环体计算方向
一开始写的循环体判断条件如下,有什么不对呢?
for (int i=1; i<=n; i++) {
for (int j=1; j<=m; j++){
if (a[i]<=j && (F[j-a[i]]+a[i])>=F[j] && (F[j-a[i]]+a[i])<=j){
...
}
else{
...
}
}
}
分析:第3行比较F[j-a[i]]+a[i]) 和 F[j]大小时,F[j-a[i]]可能是本轮已更新过的值而非预期的上一轮的值,两个值同时更新的话相当于把a[i]选了两遍,所以就出错了。
解决方法:把第二层循环倒着来就能避免上述问题;另外F[j-a[i]]+a[i]<=j的判断条件没必要加,因为在a[i] <= j时这个判断恒成立。代码修改为:
for (int i=1; i<=n; i++) {
for