【背包题解】DP代表了走到阶段i 的所有路线的最优解

1889:【提高】多重背包(2)

二维费用背包

2075 - 最大卡路里

1928 - 采购礼品

感谢



背包容量:(c)

6

重量

weight

2

2

4

6

2

1

2

3

4

5

价值

value

3

6

5

5

8

1

2

3

4

5

wv
dp数组:记录有i件物品,背包容量为j的情况下,最大价值
nameweightvalue123456
a23033333
b2606/66+dp[i-1][j-w[i]]=999
c45
d65
e28

第i件物品  

w[i]>c:放不下,最大价值=i-1件物品讨论时的最大价值

选:剩余容量=c-w[i],最大价值=v[i]+(i-1件物品,容量在 c-w[i]的情况下最大价值)

w[i]<=c:放得下

不选:最大价值=i-1件物品讨论时的最大价值

dp[i][j]

w[i]>c:放不下,最大价值 =dp[i-1][j]

选,最大价值 =v[i]+ dp[i-1][c--w[i]]

w[i]<=c:放得下,最大价值

不选,最大价值 =dp[i-1][j]

动态转移方程:

dp[i][j]=max(dp[i-1][j],v[i]+dp[i-1][j-w[i]])

1889:【提高】多重背包(2)

题目描述

有 N 种物品和一个容量是 V的背包。

第 ii种物品最多有 si​ 件,每件体积是 vi​,价值是 wi​ 。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。

输出最大价值。

关于 DP要理解的关键点:

1 DP的本质

求有限的集合中的最值(个数)

本质上,DP代表了走到阶段i 的所有路线的最优解

2 DP需要思考的点:

(1)DP 的状态是什么?状态要求什么:最大、最小、数量?

(2)DP   的状态计算?

状态转义方程;

求解方法: a 、递推  b、考虑阶段i  (最后一个阶段的值)的值是如何得来的

(3)DP   的边界是什么?

关键术语:阶段、状态、决策(状态转移方程)、边界;

以数塔问题(1216:【基础】数塔问题)为例,理解DP 的本质,再理解01背包的本质 (1282:【提高】简单背包问题);

经典的 DP模板题要熟练掌握,熟记状态转义方程!

本题解题的关键点:二进制优化(类似压缩的思想)

( 1 ) 有n 个不同的物品,要讨论2"种选择的可能(每个物品选或者不选);

(2)一个物品有n 件,虽然要讨论2"种选择的可能,但由于n 个物品是一样的,那么 就减少了讨论数量,比如:有4个物品,如果是不同物品的选2个,选12、23是不同的 选择,但如果是相同的物品,选哪两个就都是一样的了。

因此,n 个物品,要讨论的可能就分别是:选0个、选1个、选2个、选3个…选n 个。 (3)要将0~n 个不同的选择表达出来,比较简单的方法是将n 二进制化。

比如:整数7,只需要用124三个数任意组合,就能组合出0~7这8种可能。

再比如:整数10,只需要用1243(注意最后一个数),就能组合出0~10这11种可 能,这样 n 这个值就被二进制化了。

因此如果要讨论10个一样的物品,就转化为讨论4个不同的物品了;而n 个一样的物 品,就转化为log₂n 个不同的物品进行讨论。

dp[j]=max{dp[j],dp[j-w[i]]+v[i]}

#include <bits/stdc++.h>
using namespace std;

const int N=20010;
int v[N],w[N],dp[2010];
int n,m;//n种物品,背包容量为m
int vi,wi,si;
int k=0;
int main() {
	cin>>n>>m;
	for(int i=1; i<= n; i++) {
		cin>>vi>>wi>>si;
		/*对si二进制化,比如:
		有10件一样的物品我们转换为有4件不同的物品:1 2 4 3
		这4种物品的体积分别是:1*vi 2*vi 4*vi 3*vi*/
		int t=1;//权重,表示2的次方
		while(t<= si) {
			k++;
			v[k]= t* vi;
			w[k]=t* wi;
			si =si-t;
			t=t*2;
		}
//如果二进制化有剩余,存入
		if(si >0) {
			k++;
			v[k]= si * vi;
			w[k]= si * wi;
		}
	}

//01 背包
	for(int i=1; i<= k; i++) {
		for(int j= m; j >= v[i]; j--) {
			dp[j]= max(dp[j],dp[j-v[i]]+w[i]);
		}
	}
	cout<<dp[m];
	return 0;
}

二维费用背包

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同 时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品 可以得到最大的价值。设这两种代价分别为代价1和代价2,第i 件物品所需的两种代价分  v[i]   w[i]

两种代价可付出的最大值(两种背包容量)分别为maxv  maxw, 物品的价值为c[i]

解决方法: 费用加了一维,只需状态也加一维即

 f[i][j][k]       表示前i 件物品付出两种代价分别,背包体积j,  背包的承重为k 时可

获得的最大价值。

状态转移方程就是:

f[i][j][k]=max(f[i-1][j][k],f[i-1][j-v[i]][k-w[i]]+c[i])

空间优化后,可以用二维数组求解。

f[j][k]=max(f[j][k],f[j-v[i]][k-w[i]]+c[i])

2075 - 最大卡路里

题目描述

神州飞船准备运送一批食品到太空站,该飞船能够运送食品的重量、体积都有严格的限制。

现已知 nn 件完全不同的食品,每种食品的重量、体积及该食品能够提供的卡路里的值,请你编程计算出,该飞船最多能够运送多少卡路里的食物?

#include <bits/stdc++.h>
using namespace std;

const int N=410;
int dp[N][N];//代表求解体积为j,重量为k时能够得到的最大价值
int n,v,w,c;
int maxv,maxw;//背包的上限
int main() {
	cin>>maxv>>maxw;
	cin>>n;
	for(int i=1; i<= n; i++) {
		cin>>v>>w>>c;
		//01 背包
		//从最大体积~当前物品体积降序循环,同理重量也要降序循环
		for(int j= maxv; j >= v; j--) {
			for(int k=maxw; k >= w; k--) {
				dp[j][k]= max(dp[j][k],dp[j-v][k-w]+c);
			}
		}
	}
	cout<<dp[maxv][maxw];//最大价值
	return 0;
}

1928 - 采购礼品

题目描述

王老师来到商店为同学们采购礼品。

这家店有 n 种礼品(编号是 1∼n ),每种礼品只有 1 件。老板为了促销,对礼品进行搭配销售,有关联性的礼品必须都要采购(奇怪的规定),比如 1 号礼品和 3号礼品搭配了,3 号和 8 号礼品搭配了,那么王老师想要买 1 号礼品的话,就需要把 3 号和 8 号礼品都买了。

现给定每种礼品的价钱和价值,请问在有限的钱 w 的情况下,能够买到礼品的最大价值是多少?

输入

第一行输入三个整数,n,m,w,表示有n 种礼品,m 个搭配和你现有的钱的数目。

第二行至 n+1 行,每行有两个整数,c、d,表示第 ii 种礼品的价钱和价值。(1≤c,d≤10^5)

第 n+2 至 n+1+m 行 ,每行有两个整数,u、v,表示 u 号礼品和 vv 号礼品是有关联的,已经形成搭配销售的关系。

数据范围:

1≤n,w≤10^4,0≤m≤5 *10^3。

输出

一行,表示可以获得的最大价值。

样例

输入

5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2

输出

1

典型的01背包,但要求同一组的物品都要购买。我们可以采用并査集将同一组有关系的礼品的价值、价格汇总到该集合的根节点上,这样就保证了一个集合中的礼品都购买的情况。

它解决的是在有限的预算 w 下,如何选择一组关联的礼品(根据提供的搭配信息),使得这些礼品的价值总和最大。

  1. 定义了几个数组:f 用于并查集,存储物品之间的关系;qv 分别存储物品的价钱和价值;dp 用于动态规划,记录在给定背包容量下的最大价值。

  2. 首先通过并查集对具有关联关系的物品进行合并,确保在考虑搭配时,每个组合中的所有物品都被视为一个整体。

  3. 然后,遍历所有物品,如果某物品不是其自身的根节点,说明这个物品已经被包含在某个组合中,需要更新根节点的价钱和价值。

  4. 接着进行动态规划计算。从背包容量 w 到每种物品的价钱(降序),尝试是否可以添加当前物品,如果可以,更新背包的最大价值。

  5. 最后,输出在给定预算 w 下可以获得的最大价值。

  6. 王老师需要在有限的预算 w 下,选择价值最高的礼品组合,但受到了一些特定的搭配规则影响,即如果一种礼品被选中,与其搭配的相关礼品也必须被同时购买。

  7. 具体步骤如下:

  8. 理解问题: 首先,我们需要知道每种礼品的价值(d)和价格(c),以及哪些礼品之间存在强制搭配关系(由uv表示)。这是一个二维背包问题,因为搭配关系限制了我们不能单独选择某件礼品。

  9. 模型构建: 可以考虑使用动态规划的方法,比如创建一个二维数组 dp[i][j],其中 i 代表剩余的预算,j 代表剩余的可选礼品数量。dp[i][j] 表示在剩余预算 i 和可以选择的礼品数量 j 下,能获得的最大价值。

  10. 状态转移: 对于每个礼品 k,有两种情况:选择它(增加价值 d[k] 但减少可用预算 c[k]),或不选择它。根据这两种情况,更新 dp 数组。

  11. 处理搭配关系: 在更新 dp 时,要考虑已有的搭配关系。如果礼品 kl 搭配,意味着在包含 k 的情况下,l 也会被强制选择。因此,我们需要更新 dp 时包括这种情况。

  12. 寻找最大价值: 最终的答案就是 dp[w][n],即在预算 w 和所有礼品都可选的情况下,能获得的最大价值。

  13. 输出结果: 返回计算得到的最大价值作为答案。

#include <bits/stdc++.h>
using namespace std;

int f[10100];//存储物品之间的关系
int q[10100],v[10100];//价钱、价值
int dp[10100];//以拥有的钱来定义背包容量
//查:查询元素的根
int find(int x) {
	return f[x]==x?x:f[x]=find(f[x]);
}
//并:合并元素xy
void merge(int x,int y) {
	int fx = find(x);
	int fy = find(y);
	if(fx != fy) {
		f[fx]=fy;
	}
}
int main() {
	int n,m,w;
	cin>>n>>m>>w;//n个物品的价钱和价值
	for(int i = 1; i<= n; i++) {
		cin>>q[i]>>v[i];
		//并查集初始化
		f[i]= i;
	}
	//m个物品的关系
	int x,y;
	for(int i = 1; i<= m; i++) {
		cin>>x>>y;
		merge(x,y);
	}
	//将有关系的物品合并到这组物品的根上司
	for(int i = 1; i<= n; i++) {
		//该物品不是根,则将价钱和价值都合并到根上
		if(f[i] != i) {
			q[find(i)]+=q[i];
			v[find(i)]+=v[i];
			//将该组物品的价钱和价值清零
			q[i]=0;
			v[i]=0;
		}
	}
	//01背包计算结果
	for(int i = 1; i <= n; i++) {
		//从背包容量(有多少钱)~该物品的价钱降序
		for(int j= w; j >= q[i]; j--) {
			dp[j] = max(dp[j],dp[j-q[i]]+v[i]);
		}
	}
	cout<<dp[w];

	return 0;
}

 

感谢

如若本文对您的学习或工作有所启发和帮助,恳请您给予宝贵的支持——轻轻一点,为文章点赞;若觉得内容值得分享给更多朋友,欢迎转发扩散;若认为此篇内容具有长期参考价值,敬请收藏以便随时查阅。

每一次您的点赞、分享与收藏,都是对我持续创作和分享的热情鼓励,也是推动我不断提供更多高质量内容的动力源泉。期待我们在下一篇文章中再次相遇,共同攀登知识的高峰!

  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汉子萌萌哒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值