Coins(多重背包)

Whuacmers use coins.They have coins of value A1,A2,A3...An Silverland dollar. One day Hibix opened purse and found there were some coins. He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch.

You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.

Input
The input contains several test cases. The first line of each test case contains two integers n(1 ≤ n ≤ 100),m(m ≤ 100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1 ≤ Ai ≤ 100000,1 ≤ Ci ≤ 1000). The last test case is followed by two zeros.
Output
For each test case output the answer on a single line.
Sample Input
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
Sample Output
8
4
题意:有n种货币和手表价格为m,单位货币的价值有val[i],单位货币的数量有vol[i], 。求解带哪些 货币val*货币数量vol 可将手表买回且身上的钱总价值最大 题解:注意时间上的优化,为避免超时,将多重背包问题分成完全背包和01背包问题,01背包再用二进制优化
#include<iostream> 
#include<algorithm>
#include<string.h>
#define INF 0x3f3f3f3f
using namespace std;
//手表价格 
int m; 
//单位货币的价值
int val[1005];
//单位货币的数量
int vol[1005];
int dp[100005];
void Zero(int cost){
	for(int i=m;i>=cost;i--)
		dp[i]=max(dp[i],dp[i-cost]+cost);
}
//val*vol>>m,硬币可以无限使用,可以使用完全背包 
void Com(int cost){
	for(int i=cost;i<=m;i++)
		dp[i]=max(dp[i],dp[i-cost]+cost);
}
//拆分vol,价格系数k*vol组成新硬币 
void mul(int val,int vol){
	if(val*vol>=m)
		Com(val);
	else{
		int k=1;
		while(k<=vol){
			Zero(k*val);
			vol-=k;
			k*=2;
		}
		Zero(vol*val);
	}
}
int main(){
	//货币的种类 
	int n;
	while(cin>>n>>m,(n||m)){
		for(int i=1;i<=n;i++)
			cin>>val[i];
		for(int i=1;i<=n;i++)
			cin>>vol[i];
		memset(dp,0,sizeof(dp));
		for(int i=0;i<=m;i++)
			dp[i]=-INF;
		dp[0]=0;
		for(int i=1;i<=n;i++)
			mul(val[i],vol[i]);
		int ans=0;
		for(int i=1;i<=m;i++)
			if(dp[i]>0)
				ans++;
		cout<<ans<<endl;
	}
	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(&#39;inf&#39;)] * (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、付费专栏及课程。

余额充值