【PAT-A1068】Find More Coins(DP)

【题意】

有N枚硬币(可以有面值相同的),给出每枚硬币的面值,然后要求用这些硬币去支付价值M的物品,要求选出来支付的硬币面值之和必须为恰好等于M。要求按照递增顺序输出选出硬币的面值,如果答案不唯一,那么输出字典序最小的一组,如果无解,则输出"No Solution"。

【思路】

  1. 这是一个典型的01背包问题,只不过这里的同一面值硬币可以有多个,但是又不是无限个,所以可以把所有同一面值的硬币都看看成独立的,那么就和01背包问题完全等价了。还有一个问题就是这里没有了01背包问题中的v[i] ,这里每个对象(硬币)只有一个属性就是面值w[i],那要怎么解决呢?这里其实可以直接把w[i]作为v[i],因为这样的话如果求解过后dp[n][V] == V的话,那么说明可以找到答案,否则就没有答案。
  2. 怎么记录选择的硬币?可以同样开一个二维数组choice[i][v],chioce[i][v] == 1说明付总价为v时选了i号硬币,否则就是没有选。最后再对choice[i][m](i = 1,2,3…n)遍历找出其中为1的即可。
  3. 答案不唯一的时候怎么找出字典序小的。因为原来的序列并没有任何顺序要求,如果答案唯一的话都可以解出来。但是答案不唯一的话,就要看遇到更新条件相同时怎么选了,因为字典序涉及到大小关系,而且题目也要求从小到大输出选出的硬币面值,况且初始的硬币顺序对答案唯一的情况下解出答案无影响,所以不如先对所有硬币按照面值大小升序排序,这样求解出来的答案就能按照输出顺序来排列并可以在求解过程中选择字典序小的作为最终答案。但是,我按照预想的过程进行求解之后发现答案错误,参考了上机指南上的解法,我的解法只在排序顺序和输出顺序上相反,其余都没有区别,但是从表面上分析只要排序顺序和输出顺序一致(这里的一致是因为要求以字典序小的输出,且从后面的讨论可以知道输出的时候必须倒着判断,所以排序也得降序,否则先输出的就是大的数了),正着求和逆着求应该都正确。为此思考了很久都每找到原因,最后,没办法,我用一个简单的例子去模拟了自己的算法的求解过程,终于找到了问题所在,这也提醒我在以后的学习刷题过程中,当从理论上思考找不出问题的时候,可以用简单的实例去从实际追踪算法求解过程,看算法是如何运行的,这样就能直观地找到原因
  4. 其实问题出在了那些无解的情况,在dp[][]数组中,那些无解的部分虽然可以通过dp[i][m] != m这样判断出来无解,但是它们在求解过程中的值也是会被顺带更新的,这样了造成了最后在记录最优路径的时候对正确答案造成影响,比如如果正着记录最优路径,那么就会从choice[1][v]开始,这个值在很多情况下都是无解的也就是对的dp[i][v] != v,但是呢,无解并不意味着它在求解过程中它没有被更新,相应的choice[i][v]也会被更新为1,这样记录最优路径的时候就会从一个无解的值开始,自然答案就不可能对了。下面再来看看为什么倒着记录就可以。倒着就会是从choice[n][v]开始,也就是我们要的最终答案,这个值要么是正确的,要么就是不符合dp[n][v] == v,这个就是无解的情况,直接输出无解了,所以要进入输出最优路径的部分的话,这个值一定的正确的,这样就能保证永远从正确的值出发找路径,就能够避开那些无解的值,输出最优路径。

【注意点】

  1. 先用二维dp数组的情况搞清楚再写一维的,或者如果空间复杂度允许的话就用二维的,不容易出错。
  2. 以后遇到这类要记录最优选择路径的DP问题,都记得从最终态倒着往回判断,这样才保证不会碰到无解的值。
  3. 求解dp数组时,如果两种策略的大小相等,则应该选择放第i件物品的策略,因为这意味着,不放的情况是几个较重的物品重量之和为v,而放的话也能满足物品重量之和为v,说明这虽然两条路径都能满足重量之和为v,但是放第i件物品的这条路径放入的物品件数更多,在物品的重量判断顺序为从小到大的情况下,一定为物品件数多(也就是路径上结点更多)的情况字典序更小。举个例子,假设总重量为5, 可以有两种情况满足质量和为5,第一个是2+3, 另一个是1 + 2 + 2,那么一定是后者的字典序更小,还有就是也可能为两种策略的路径上选择的物品数量相同的情况,比如不选i号物品的策略对应的方案为2+3,i号物品的重量为4,那么此时选择放入4的方案得到了就是1+4显然也是放入的策略字典序更小,更优。

纸上得来终觉浅,绝知此事要躬行。经过这一次探究,我对动态规划,对背包问题的理解也更深刻了。

【代码】

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;
const int maxn = 10010;
const int maxm = 110;
const int INF = (1 << 30) - 1;
int dp[maxn][maxm];
int w[maxn];
bool choice[maxn][maxm] = { false };
bool cmp(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);
	memset(dp[0], 0, sizeof(dp[0]));
	bool flag = false;
	for (int i = 1; i <= n; i++)
	{
		for (int v = m; v >= w[i]; v--)
		{
			if (dp[i - 1][v] > dp[i - 1][v - w[i]] + w[i]) {
				dp[i][v] = dp[i - 1][v];
				choice[i][v] = false;
			}
			else
			{
				dp[i][v] = dp[i - 1][v - w[i]] + w[i];
				choice[i][v] = true;
			}

		}
		if (dp[i][m] == m) {
			flag = true;
		}
	}

	if (flag == false) {
		printf("No Solution\n");
	}
	else
	{
		int cnt = 0;
		for (int i = n; i > 0; i--)
		{

			if (choice[i][m] == true) {
				if (cnt != 0) {
					printf(" ");
				}
				m = m - w[i];
				printf("%d", w[i]);
				cnt++;
			}
		}
	}

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值