Uva 674 Coin Change 完全背包

题目链接:here

题意:5种硬币,输出组合数

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 7500;
int coin[5] = {1,5,10,25,50};
int dp[maxn];
int m;

int main()
{
	int i, j;
	memset(dp, 0, sizeof(dp));
	dp[0] = 1;
	for (i=0; i<5; i++)
	{
		for (j=coin[i]; j <= 7500; j++)
		{
			if (j >= coin[i])
				dp[j] += dp[j-coin[i]];
		}
	}
	while (scanf("%d", &m) != EOF)
	{
		printf("%d\n", dp[m]);
	}
	return 0;
}


<think>嗯,我现在要理解一下01背包完全背包问题之间的区别。这两个都是动态规划里的经典问题,对吧?让我先回忆一下,01背包是说每个物品只能选一次,而完全背包可以选无限次。但具体它们的解法有什么不同呢? 首先,01背包的基本思路是,对于每个物品,我们有两种选择:放或者不放。那状态转移方程应该是dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i])。这里i是前i个物品,j是当前背包容量。这时候,二维数组的情况下,每个状态都是基于上一行的结果来更新的。那如果优化成一维数组的话,应该是逆序遍历容量,也就是从大到小,这样保证每个物品只被计算一次。 那完全背包的话,因为每个物品可以选多次,所以状态转移方程应该是怎样的呢?比如,当考虑第i个物品的时候,可能已经选过k次了。这时候可能需要遍历k的次数,但这样时间复杂度会很高。或者有没有更优化的方法? 哦,对了,完全背包的状态转移方程可以优化成dp[i][j] = max(dp[i-1][j], dp[i][j - weight[i]] + value[i])。这里的不同点在于,当选择放入物品i的时候,不是从i-1的状态转移过来,而是仍然在i的状态,因为可以重复选。这样的话,如果用一维数组的话,是不是应该正序遍历容量?因为这样在更新dp[j]的时候,可能已经考虑了当前物品的多次放入情况。比如,当j从小到大遍历时,当处理到j的时候,j - weight[i]可能已经被更新过,也就是可能已经放入了当前物品,所以这时候加上value[i]就相当于多次选择。 比如,假设物品的重量是2,价值是3。当处理容量j=4时,先更新j=2的位置,这时候放入一个物品,然后当j=4的时候,可以再次放入,这样总价值就是6。这样正序遍历的话,就可以处理多次选择的情况。而01背包逆序的话,每个物品只能选一次,因为每次更新都是基于未选过该物品的状态。 那总结一下,两者的区别主要在于物品可以选的次数不同,导致状态转移的方式不同。在二维数组的情况下,完全背包用的是同一行的前面的状态,而01背包用的是上一行的状态。转换到一维数组时,完全背包要正序遍历容量,而01背包要逆序遍历。这样就能保证状态转移的正确性。 举个例子,比如物品的重量为1,价值为2,背包容量为3。01背包的话,只能选三次不同的物品吗?或者每个物品只能选一次?假设只有一个物品,那么01背包只能选一次,所以总价值是2。而完全背包的话,可以选三次这个物品,总价值是6。这时候两者的处理方式就会不同。 那在代码实现上,01背包的一维数组版本是: for i in 0..n-1: for j = C downto weight[i]: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) 而完全背包则是: for i in 0..n-1: for j = weight[i] to C: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) 也就是内层循环的顺序相反。这样的话,完全背包允许同一物品多次被加入,而01背包只能加入一次。 那是否还有其他区别呢?比如初始化条件?可能初始化都是类似的,比如dp[0] = 0,其他初始为0或者负无穷,取决于问题具体要求。 那两者的应用场景有什么不同?比如,01背包适用于物品数量有限的情况,比如每个物品只能取一次,比如挑选宝石。而完全背包适用于物品可以无限取的情况,比如硬币问题中的硬币无限供应,求组成金额的最少硬币数或者最大价值等。 比如,硬币兑换问题,如果硬币可以重复使用,那就是完全背包问题。而如果每个硬币只能用一次,那就是01背包问题。当然,这类问题可能还需要不同的状态转移方程,比如求最少硬币数的话,可能状态转移方程是dp[j] = min(dp[j], dp[j - coin] + 1)。 那还有,如果问题中的物品有多个数量,比如每个物品最多取k次,这时候可能要用多重背包问题,而多重背包可以转化为01背包或者完全背包吗?比如通过二进制拆分或者单调队列优化? 总的来说,01背包完全背包的主要区别在于物品的使用次数,导致在动态规划的状态转移时处理方式不同,从而内层循环的顺序相反。这也是为什么在优化为一维数组后,两者的遍历顺序不同的原因。</think>01背包完全背包是动态规划中的两类经典背包问题,核心区别在于**物品的选取次数**,这直接影响状态转移方程的设计和实现细节。以下是两者的对比介绍: --- ### **1. 核心区别** - **01背包** - **物品限制**:每个物品只能选**0次或1次**(选或不选)。 - **典型问题**:在容量有限的背包中装入物品,使总价值最大(每个物品唯一)。 - **状态转移方程**(二维数组): ```python dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]) ``` 其中 `i` 表示前 `i` 个物品,`j` 表示当前背包容量,`w[i]` 和 `v[i]` 分别为物品的重量和价值。 - **一维数组优化**: 内层循环**逆序**遍历容量(从大到小),确保每个物品仅被计算一次。 ```python for 物品i in 所有物品: for j in range(C, w[i]-1, -1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) ``` --- - **完全背包** - **物品限制**:每个物品可以选**无限次**。 - **典型问题**:硬币兑换(无限硬币凑出指定金额)、无限资源分配等。 - **状态转移方程**(二维数组): ```python dp[i][j] = max(dp[i-1][j], dp[i][j - w[i]] + v[i]) ``` 注意:选择物品时参考的是**同一行**(允许重复选取)。 - **一维数组优化**: 内层循环**正序**遍历容量(从小到大),允许同一物品多次被加入。 ```python for 物品i in 所有物品: for j in range(w[i], C+1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) ``` --- ### **2. 直观对比** - **遍历顺序** - 01背包:逆序更新(避免重复计算)。 - 完全背包:正序更新(允许重复计算)。 - **状态依赖** - 01背包:当前状态仅依赖**上一行**的结果。 - 完全背包:当前状态依赖**本行已更新的结果**。 --- ### **3. 应用场景示例** - **01背包** - 问题:有 `N` 件物品,第 `i` 件重量 `w[i]`、价值 `v[i]`,背包容量 `C`,求最大价值。 - 示例代码(一维数组): ```python dp = [0] * (C+1) for i in range(N): for j in range(C, w[i]-1, -1): dp[j] = max(dp[j], dp[j - w[i]] + v[i]) ``` - **完全背包** - 问题:硬币面值 `coins = [1, 2, 5]`,求凑出金额 `amount` 的**最少硬币数**。 - 示例代码(一维数组): ```python dp = [float('inf')] * (amount + 1) dp[0] = 0 for coin in coins: for j in range(coin, amount+1): dp[j] = min(dp[j], dp[j - coin] + 1) ``` --- ### **4. 核心总结** | **特征** | **01背包** | **完全背包** | |-------------------|---------------------------|---------------------------| | 物品选取次数 | 0或1次 | 无限次 | | 状态转移依赖 | 上一行的状态 | 本行已更新的状态 | | 一维数组遍历顺序 | 逆序(从大到小) | 正序(从小到大) | | 典型问题 | 唯一物品的最大价值 | 无限资源的组合优化 | 理解两者的差异关键在于**物品是否可重复选**,这直接决定了状态转移的方向和代码实现中的遍历顺序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值