猫史档案馆


背包九讲——第一讲:01背包(动态规划)(萌新退避)

用户:fx已退fx已退查看:2 回复:1 评论:2 创建时间:2021-10-21T19:41:59


本教程特别难!前方高能!萌新退避!!!

emotion_编程猫_冷漠emotion_编程猫_冷漠emotion_编程猫_冷漠emotion_编程猫_冷漠emotion_编程猫_冷漠emotion_编程猫_冷漠emotion_编程猫_冷漠

-------------------------------------------------------------------------

一、我们举个栗子来理解它

假设你是个小偷,被这一个可以装四千克的背包。

你可以喵的商品有3件:

音响       笔记本电脑        吉他

3000元                      2000元                         1500元

 4千克                         3千克                           1千克

为了让喵的商品价值更高,你该选择商品?

方法1

最简单的算法——枚举

就是一个个枚举出来,时间复杂度是O(2^n),每增加一件商品,所用的时间就会以几何倍喵!真的是慢如蜗牛。

看图!

center_image

方法2

动态规划!

这是一种解决棘手问题的方法,它将大问题分成一个个子问题,并先着手解决子问题。

对于01背包问题,你先解决子背包问题,在逐步解决原来的问题。

我们先来演示一下这种算法的执行过程。看完过程后我们再写代码。

每一个动态规划都从一个网格开始:

center_image网格最初是空的,让我们定义一下:i代表行,j代表列,而这个表格就叫dp(动态规划的简写)。

那么dp[i][j]的定义又是什么呢?不急,我们先填满这个网格。

1.吉他行

第一个单元格表示背包的容量为1kg,吉他的重量也是1kg,这意味着它可以装入背包!因此这个单元格包含吉他,价值1500元。

center_image

与这个单元格一样,每个单元格都将包含当前可装入背包的所有商品。

center_image

别忘了,这是第一行,只有吉他可供你选择。换言而之,你假装现在还没发喵其他两件商品。

2.音响行

我们来填充下一行——音响行。你现在处于第二行,可偷的商品有吉他和音响。在每一行,可偷的商品都为当前行的商品以及之前各行的商品。因此,当前你还不能偷笔记本电脑,而只能偷音响和吉他。我们先来看第一个单元格,它表示容量为1kg的背包。在此之前,可装入1kg背包的商品的最大价值为1500元。

center_image

该不该偷音响呢?

不不不!音响太重了!所以还是1500元。

接下来的两个单元格的情况与此相同

center_image

现在背包的容量为4kg,终于够装下音响了!原来的最大价值为1500元,但如果在背包中装入音响而不是吉他,价格将为3000元!因此还是偷音响吧。

center_image

你更新了最大价值!在这个网格里,你将逐步更新最大价值。

3.笔记本电脑行

下面一同样的方式处理笔记本电脑。笔记本电脑重3kg,没发装入容量为1kg或2kg的背包,因此前两个单元格的最大价值还是1500元,但到了第三个单元格,我们有了一个3kg的背包,可以装下笔记本电脑了,最大价值为2000元。center_image

对于容量为4kg的背包,情况很有趣。这是非常重要的部分。当前最大价值为3000元,你可不偷音响,而偷笔记本电脑只值2000元。

3000 VS 2000

  音响                 笔记本电脑

价值没有原来高。但等一等,笔记本电脑的重量只有3kg,背包还有1kg的容量没用!

3000 VS (2000 + 剩下容量的价值)

     音响                       笔记本电脑                   余下1kg的价值

就是:

3000 VS (2000 + 1500)

     音响                       笔记本电脑              吉他

所以,我们选笔记本电脑和吉他!

最终网格如下:

center_image好了,我们现在来揭晓答案,dp[i][j]的意义是:                         { 1.上一个单元的值(dp[i-1][j]的值) dp[i][j] = max{                       {2.当前商品的价值 + 剩余空间的价值                                                     (dp[i-1][j-当前商品的重量]) 二、上代码 1.kitten 4.0(看不清就放大网页吧,Ctrl+鼠标滚轮):center_image看起来很繁琐对不对!看不懂就自己写一遍吧,实在不行就在评论区找大佬吧…… 2.C++:

#include <iostream>
#include <cstring>
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	int dp[n+1][m+1],weight[n+1],value[n+1];
	for(int i=1;i<=n;++i)
		cin>>weight[i]>>value[i];
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			dp[i][j]=dp[i-1][j];
			if(j>=weight[i])dp[i][j]=max(dp[i-1][j],value[i]+dp[i-1][j-weight[i]]);
		}
	}
	cout<<dp[n][m];
    return 0;
}

好了,不是大佬以下的内容就不用看了。

三、空间优化

其实我们可以把二维数组变成一维数组,就是数组滚动!

倒叙遍历是为了保证物品i只被放入一次!但如果一旦正序遍历了,那么物品0就会被重复加入多次!

C++:

#include <iostream>
#include <cstring>
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	int dp[m+1],weight[n+1],value[n+1];
	for(int i=1;i<=n;++i)
		cin>>weight[i]>>value[i];
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;++i){
		for(int j=m;j>=weight[i];--j)
			dp[j]=max(dp[j],value[i]+dp[j-weight[i]]);
	}
	cout<<dp[m];
    return 0;
}

三、我自己的一点点优化

我们可以在获取输入时输入一个数就计算一次

C++:

#include <iostream>
#include <cstring>
using namespace std;
int main()
{
	int n,m;
	cin>>n>>m;
	int dp[m+1],weight[n+1],value[n+1];
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;++i){
		cin>>weight[i]>>value[i];
		for(int j=m;j>=weight[i];--j)
			dp[j]=max(dp[j],value[i]+dp[j-weight[i]]);
	}
	cout<<dp[m];
    return 0;
}

四、最后……

首先发一下AC证明这个帖子的可用性(最上面的是优化后的):

center_image好了,相信看到这里的你们都很有耐心,听懂了的大佬在评论区扣1。 然后呢,我没写Python和Java的代码,希望有位大佬在评论区补上。 制贴不易,不喜勿喷!(不知道要过几载才会更新第二讲) emotion_编程猫_点赞emotion_编程猫_点赞emotion_编程猫_点赞emotion_编程猫_点赞emotion_编程猫_点赞emotion_编程猫_点赞emotion_编程猫_点赞


回复

上一页1 页 / 共 1下一页
86135clc86135clc

《零元购》

点赞0


评论