动态规划-HDU - 2069 Coin Change

题意

有五种硬币不限数量,面值分别是1,5,10,25,50。问你用这些硬币组成n元有多少种方式,硬币的总数要不超过100个。

思路

状态表示

如果类比01背包,那么至少我们可以认为应该用一个二维数组来记录状态,在背包问题中,我们用dp[ i ][ j ]来表示“前 i 件物品装入容量为 j 的背包所能得到的最大总价值”;在这里,我们可以尝试(事实是我也不知道为什么)用dp[ i ][ j ] 来表示“用 j 个硬币组成 k 元的方法数”。
对于dp [ 11 ]而言,它一定程度上可以由dp[ 10 ], dp[ 6 ], dp[ 1 ]…得到。于是开始我想设dp[ i ] 为第 i 个数的组成方式,再企图用之前的结果推出它,但是失败了。
二维这个定义很巧妙,我认为这也是一个对问题的拆解,牺牲了空间,存储了更多的信息,将所需求解的问题转化为求“用1、2、3…j个硬币组成k元的方法数”

状态转移方程

dp[k][j] += dp[k - type[]][j - 1]

其中 k 表示第 k 元, j 表示使用的硬币数量, type[]表示硬币种类
首先答案和硬币的种类密切相关,不同的硬币种类答案也不尽相同,因此方程中一定会出现和种类相关的量,这也意味着可能还要枚举一下不同种类的硬币。
然后我们可以想到,硬币数量为 j 的状态应该与数量为 j - 1 的状态密切相关,因此我们可能需要从“上一行”的状态来推出当前状态。
有了一个坐标,还要锁定另外一个坐标,那第 k 元的状态是和第 k - 1 元的状态密切相关吗?
这时可以举个栗子,比如dp(3,2)能否由dp(2, 1)推出?好像真可以,前者等于后者。
但是dp(10, 2)和dp(9, 1)呢?
这时我们发现,10的出现,允许我们使用新的硬币——10元,因此前者会比后者多一种方法,不仅如此,它还可以使用两个5元硬币,整整比后者多了两种方法。这印证了我们之前的想法——答案和硬币的种类密切相关。
于是我们可以猜测,dp(k, j)与dp(k - 种类,j)有关系。
为什么是“ += ”呢?这我也没什么依据(扯不下去),我感觉,dp(k,j)不能简单地由另外一项“ + 1 ”得到,但总得有个加法之类的把,于是可以考虑dp(k,j)不断累加其他项。

老实说,上面这些想法都是看了题解倒推得到的,做题的时候想法肯定会更多,希望能给下次写动态规划的我一点启示吧。

代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
const int maxcoin = 101;
const int maxval = 251;
int dp[maxval][maxcoin] = { 0 };
//dp[k][j]表示用j个硬币组成k元的方法数
int ans[maxval];
int type[] = { 1, 5, 10, 25, 50 };

int main(void) {
	dp[0][0] = 1;
	//并不是输入一个数再计算,而是先打表再回答
	for (int i = 0; i < 5; ++i) {
		for (int j = 1; j < maxcoin; ++j) {
			for (int k = type[i]; k < maxval; ++k) {
				dp[k][j] += dp[k - type[i]][j - 1];
			}
		}
	}
	//把各个子问题的答案加起来就是最后答案了
	for (int i = 0; i < maxval; ++i) {
		for (int j = 0; j < maxcoin; ++j) {
			ans[i] += dp[i][j];
		}
	}
	int n;
	while (scanf("%d", &n) != EOF) {
		printf("%d\n", ans[n]);
	}
	return 0;
}

模拟

这题顺带提供我研究流程时的代码,也可以试着手动模拟一下。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
const int maxcoin = 101;
const int maxval = 251;
int dp[maxval][maxcoin] = { 0 };
//dp[k][j]表示用j个硬币组成k元的方法数
int ans[maxval];
int type[] = { 1, 5, 10, 25, 50 };

int times = 1;
void show(int x) {//x是想要观察的 rmb 值
	printf("-----the %d times-----\n", times++);
	for (int j = 0; j <= x; ++j) {
		for (int i = 0; i <= x; ++i) {
			printf("%d	", dp[i][j]);
		}
		printf("\n");
	}
	printf("\n\n");
	return;
}

int main(void) {
	dp[0][0] = 1;
	//并不是输入一个数再计算,而是先打表再回答
	int x = 11; //x是想要观察的 rmb 值
	for (int i = 0; i < 5; ++i) {
		for (int j = 1; j <= x; ++j) {
			for (int k = type[i]; k <= x; ++k) {
				dp[k][j] += dp[k - type[i]][j - 1];

			}
		}
		show(x);
	}
	
	for (int i = 0; i < maxval; ++i) {
		for (int j = 0; j < maxcoin; ++j) {
			ans[i] += dp[i][j];
		}
	}
	int n;
	/*
	while (scanf("%d", &n) != EOF) {
		printf("%d\n", ans[n]);
	}
	*/
	return 0;
}

最后

刚刚接触动态规划,我的理解还十分有限,还有很多细节无法尽善尽美的解释(比如代码中的三层循环)。谨以本题作为起步,希望以后能慢慢领会其中的奥秘。另外最后还要感谢两篇优质的题解指点。
HDU - 2069 Coin Change(换硬币)解题报告(dp入门)
HDU - 2069 Coin Change

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值