背包问题总结(DP)

一、0-1背包问题

有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。每种物品都只有1件

基本思想

令dp[v]表示前i件物品恰好装入容量为v的背包中所能获得的最大价值。对第i件物品有两种选择策略:

  1. 不放第i件物品,问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值。
  2. 放第i件物品,问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值。

状态转移方程:dp[v] = max{dp[v], dp[v-w[i]]+c[i]}

代码
int V, n;
int dp[maxv], c[maxn], w[maxn];
fill(dp, dp + v, 0); //边界 
for(int i = 1; i <= n; i++){
	for(int v = V; v >= w[i]; v--)
		dp[v] = max(dp[v], dp[v - w[i]] + c[i]);
}
int max = 0; //找到最大的总价值 
for(int v = 0; v <= V; v++){
	if(dp[v] > max)
		max = dp[v];
}

注意:这里v的枚举顺序必须是逆序,否则dp值可能会被覆盖。
具体来说,我们是由第i-1次循环的两个状态推出第i个状态的,在第i次循环中,还没执行到v-w[i]时,我们保存的应该是第i-1次循环中得到的dp[v-w[i]],而如果是正向枚举,由于v-w[i]小于v,我们这里已经保存了第i次循环时得到的dp[v-w[i]],显然是不对的。

二、完全背包问题

有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。每种物品都有无穷件(与01背包的唯一区别),不超过容量v即可

基本思想

仍然令dp[v]表示前i件物品恰好装入容量为v的背包中所能获得的最大价值。对第i件物品有两种选择策略:

  1. 不放第i件物品,问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值。
  2. 放第i件物品,由于每种物品可以放任意件,放完第i件物品还可以继续放,所以问题不应转化为前i-1件物品的dp[v-w[i]],而是转移到第i件物品的dp[v-w[i]],因此完全背包问题适合正向枚举

状态转移方程:dp[v] = max{dp[v], dp[v-w[i]]+c[i]}

代码
int V, n;
int dp[maxv], c[maxn], w[maxn];
fill(dp, dp + v, 0); //边界 
for(int i = 1; i <= n; i++){
	for(int v = w[i]; v <= V; v++)
		dp[v] = max(dp[v], dp[v - w[i]] + c[i]);
}
//与01背包的唯一区别是正向枚举

多重背包问题

有n件物品,每件物品的重量为w[i],价值为c[i],最多有s[i]件可用。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。

朴素算法

将s[i]件物品拆分,得到物品总量为s[i]的累加,转化为0-1背包问题。(时间复杂度高)
状态转移方程:
dp[v] = max{dp[v], dp[v - j * c[i]] + j * w[i] | 0 <=j <= s[i]}
代码如下:

int V, n;
int c, w, s; //价值 重量 数量 
int dp[maxv] = {0};
for(int i = 0; i < n; i++){
	scanf("%d%d%d", &c, &w, &s);
	for(int j = 1; j <= s; j++){
		for(int v = V; v >= j * w; v--)
			dp[v] = max(dp[v], dp[v - j * w] + j * c);
	}
}
二进制优化

基本思路:
将第i种物品分成若干件物品,每件物品为1,2,4,…,2k-1,s[i]-2k+1个i物品放在一起,则最终第i种物品的数量由原先的s[i]变为log(s[i]),每件物品的重量与价值均改变。假设我们有14件i物品,则将其分为4堆:1件、2件、4件、7件。于是将这四堆物品任意组合,我们可以得到14种可能性,即1~14的所有物品数量(14的二进制为1110,而四个堆的数量二进制为1,10,100,111,前三个堆组合可得到1-7,第四个堆和其他的组合可得到8-14)。这样做可避免枚举s[i]件物品,降低复杂度。
之后就可将其转化为0-1背包问题,把每一堆的logs[i]件物品拆分。
代码如下:

int V, n, cnt = 0;
int c, w, s; //价值 重量 数量 
int dp[maxv] = {0};
int val[maxn], cost[maxn]; //分堆后每个堆的物品重量和总价值 
for(int i = 0; i < n; i++){
	scanf("%d%d%d", &c, &w, &s);
	for(int j = 1; j <= s; j *= 2){
		val[++cnt] = w * j;
		cost[cnt] = c * j;
		s -= j;
	}
	val[++cnt] = w * s;
	cost[cnt] = c * s;
}
//对cnt种物品进行0-1背包处理 
for(int i = 1; i <= cnt; i++)
	for(int v = V; v >= val[i]; v--)
		dp[v] = max(dp[v], dp[v - val[i]] + cost[i]);
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值