动态规划-背包九讲:入门组

什么是背包

问题可以描述为:
给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
什么?你看不懂?哈哈哈嗝:)
说白了,就是放一堆东西到一个容器,让容器能装的东西价值最大

背包的分类

  • 1.01背包
  • 2.完全背包
  • 3.多重背包(我包含在分组背包内了)
  • 4.混合背包
  • 5.二维费用
  • 6.分组背包
  • 7.依赖背包
  • 8.背包方案数

本题解只介绍和详细讲解打钩的背包,如果不服,来打我啊!
对了,好人做到底,抄代码的小伙子 我给个链接吧,23333~
https://me.csdn.net/qq_35436309
你什么都没看见,对不对?
好的,我们继续瞎扯蛋。

背包的原理

相信聪明的你,一定学了背包,(废话,不学背包来看什么题解?),所以我们不用讲原理了吧? (日常被打)
好好好,接下来是重点:
利用搜索
不是,是简单的DP,说白了,就是难一点的 递归
好了好了,废话了久,我也该切入主题了。

背包四连

在这里插入图片描述
错了错了,不是这张图,是:
在这里插入图片描述

01背包:

描述
一个旅行者有一个最多能装 M 公斤的背包,现在有 n 件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn,求旅行者能获得最大总价值。
输入
第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30); 第2…N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
输出
仅一行,一个数,表示最大总价值。
输入样例 1
10 4
2 1
3 3
4 5
7 9
输出样例 1
12
来源
一本通

首先,我们来说一说背包入门
我们以01背包为例:

科普:01背包为什么叫01背包呢?
它总要有个名字啊,不然它叫10背包,你还不是要问为什么
不是,是因为他只有拿和不拿,所以叫01背包(正儿八经的科普)

  • 搜索解法:
    首先,还不会背包的你,会想到怎么做呢?
    3,2,1
    答案揭晓:
    搜索(没猜到的同学也不要灰心,也许你猜到下面递推/DP方式了吧)
    搜索是个好东西,万能的,一定要学好搜索,以后加个剪枝就无敌了有木有(≧∇≦)ノ在这里插入图片描述
    哼唧,让我们继续来研究本题吧!!!
    用什么搜索好呢?
  • A.BFS
  • B.DFS
  • C.暴力搜
  • D.百度搜索
    有毒吧,肯定选B啊。C?你告诉我怎么搜嘛。D?噗呲,抄代码专用。至于A,BFS是求最短路的。所以要不加思索的选B。
//DFS的01背包AC解法
#include <bits/stdc++.h>
using namespace std;
int m,n,w[10010],c[10010];
int ans;
void dfs(int s,int v,int cc){
	if(s>n+1){
		return;
	}else{
		ans=max(ans,cc);
		if(v+w[s]<=m){
			dfs(s+1,v+w[s],cc+c[s]);
		}
		dfs(s+1,v,cc);
	}
}
int main(){
    cin>>m>>n;
    for(int i=1;i<=n;i++){
    	cin>>w[i]>>c[i];
	}
	dfs(1,0,0);
	cout<<ans;
	return 0;
}

不要抄代码,不要抄代码,不要抄代码,否则你会死的很惨

我来解释一下,这是DFS,其实就是(如下图所示)
在这里插入图片描述
感兴趣的同学,可以研究一下为什么会TLE
提示:时间复杂度
解:
RP的问题
TLE很明显,数据一大,深搜就要搜很多层,自然TLE。
所以大佬们要记得记忆化搜索哦!
这不是本章重点,略过。。。。。。(其实就是蒟蒻的兔子不会,编不下去了,23333~)

  • DP/递归解法

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。用子问题定义状态:即F[i, v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:
F[i, v] = max{F[i − 1, v], F[i − 1, v − Ci] + Wi}
这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包2中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只和前i − 1件物品相关的问题。如果不放第i件物品,那么问题就转化为“前i − 1件物品放入容量为v的背包中”,价值为F[i − 1, v];如果放第i件物品,那么问题就转化为“前i − 1件物品放入剩下的容量为v − Ci的背包中”,此时能获得的最大价值就是:
F[i − 1, v − Ci]
再加上通过放入第i件物品获得的价值Wi。
摘抄自崔添翼的背包问题九讲 2.0 alpha1

其实我也看不懂 ,就知道黑体字一定有用
现在,我们有请Mr.假老练上场
在这里插入图片描述
好的,测评嘉宾到了的话,我们开始嘛:
解:如代码所示(今天很不兔子)

//DP的01背包写法(二维)
#include<bits/stdc++.h>
using namespace std;

int a[1000],b[1000];
int dp[1000][1000];
int n,m;
int v;
int main(){
	cin >> m>>n;
	for(int i=1;i<=n;i++){
			cin >> a[i]>>b[i];
	}
	for(int i=1;i<=n;i++){
		for(int j=m;j>0;j--){
		    if(a[i]<=j){
			dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+b[i]); 
	    }else{
	    	dp[i][j]=dp[i-1][j];
	    	} 
		}
	}
	cout << dp[n][m];
	return 0;
}

哦,这样啊,思路是这样、然后这样、再那样、最后再来一个那些(兔子又皮起来了,不行,要一本正经的胡说八道
这是背包的简单题,如图:

我们可以假设dp[i][j],表示前i件物品,重量之和不超过j并是最优价值。
那么:
dp[i][j]=max(dp[i-1][j-a[i]]+b[i],dp[i-1][j])
其实,a就是重量,b就是价值。
所以,dp[n][m]就是最优的方案。
黑体乃精髓也!递推公式,解题根源。
小结:以后一般的背包和DP都要推出转移式。

完全背包:

描述
设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。
输入
第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30); 第2…N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
输出
仅一行,一个数,表示最大总价值。
输入样例 1
10 4
2 1
3 3
4 5
7 9
输出样例 1
max=12
来源
一本通

写到这里,兔子真的是迷茫的,好像自己什么都懂了,但讲不出来写不出来 ???
这不就是01背包的翻版吗??
改一下代码,先推导一波:
dp[i][j]=max(dp[i][j-w[i]]+c[i],dp[i-1][j])
没错,就是它,万恶的 核心。
它怎么来的呢???
在这里插入图片描述

如上图所示
来一波代码秀:

//DP的完全背包写法(二维)
#include<bits/stdc++.h>
using namespace std;

int w[100100],v[100100];
int dp[1010][1010];

int main(){
    int t,m;
    cin>>t>>m;
    
    for(int i=1;i<=m;i++){
        cin>>w[i]>>v[i];
    }
    
    
    for(int i=1;i<=m;i++){
    	for(int j=t;j>=0;j--){
            for(int k=0;k*w[i]<=j;k++){
            	
					dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]);//递推
            	
			}
            
                         
        }
	} 
        
    cout<<"max="<<dp[m][t];
    return 0;
}

混合背包:

描述
一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W_1,W_2,…,W_n,它们的价值分别为C_1,C_2,…,C_n。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
输入
第一行:二个整数,M(背包容量,M≤200),N(物品数量,N≤30); 第2…N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件,若为其他数字,则为此物品可购买的最多件数(Pi)。
输出
仅一行,一个数,表示最大总价值。
输入样例 1
10 3
2 1 0
3 3 1
4 5 4
输出样例 1
11
提示
样例说明 选第一件物品1件和第三件物品2件。
来源
一本通

接下来进行下一个话题:母猪的产后护理 拿错书了
在这里插入图片描述
不是,是:
三个背包混合。

三个背包混合?
你是魔鬼还是秀儿?
不,我是魔秀:)

我们可以将它拆分成几个问题,再逐一破解。
将它化成01背包和完全背包混合,多重背包单独算。
至于多重背包,也没什么好讲的,背包老一套,推转移式嘛。
然后把他们像揉面一样,用 if 把它们揉到一起,想象你是泥人张~

附上代码:

//DP的混合背包写法(二维)
#include <bits/stdc++.h>
using namespace std;

int m,n;
int w[1000],c[1000],p[1000];
int dp[1000][1000];
int main(){
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		cin>>w[i]>>c[i]>>p[i];
		if(p[i]==0){
			p[i]=0x3f3f3f3f;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			for(int k=0;k*w[i]<=j&&k<=p[i];k++){
				dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*c[i]);
			}
		}	
	}
	cout<<dp[n][m];
	return 0;
}

分组背包:

描述
一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W_1,W_2,…,W_n,它们的价值分别为C_1,C_2,…,C_n,这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
输入
第一行:三个整数,V(背包容量,V≤200),N(物品数量,N≤30)和T(最大组号,T≤10); 第2…N+1行:每行三个整数Wi,Ci,P,表示每个物品的重量,价值,所属组号。
输出
仅一行,一个数,表示最大总价值。
输入样例 1
10 6 3
2 1 1
3 3 1
4 8 2
6 9 2
2 8 3
3 9 3
输出样例 1
20
来源
一本通

啊,终于到了本篇的最后一个 背包问题了,感觉好有成就感啊!
(分组背包一般不考)
这样吧,你已经能编写该程序了,我直接上代码:

//DP的分组背包写法(一维)
#include<bits/stdc++.h>
using namespace std;

int a[15][30],v[15][30],b[15],dp[205],n,t,m,x,y,h;
int main(){
	cin>>m>>n>>t;
	for (int i=1;i<=n;i++){
		cin>>x>>y>>h;
		v[h][++b[h]]=x;//b用来存第h个组有多少件物品,v为代价
		a[h][b[h]]=y;//a为价值
	  }
	for (int i=1;i<=t;i++){//组
		for (int j=m;j>0;j--){//重量
	    	for (int k=1;k<=b[i];k++){//这个组的第几件
	    		if (j>=v[i][k]){//判断是否越界
	    			dp[j]=max(dp[j],dp[j-v[i][k]]+a[i][k]);//动态转移方程
	    		}
			}
		}
	}
	cout<<dp[m];
}

抄代码的你 一定会问了,这个一维DP算哪门子写法??
嘻嘻,不要慌,接下来我们来说说最后一个专题:二维DP的压缩
什么?还有一个专题?
在这里插入图片描述

背包的压缩

有的同学会说,二维DP简单。不,那是你不会一维DP,你学会了一维DP,一定会更简单的,从此代码二维DP再不存在。

二维,2*m的形式:

首先,我们先来研究一下DP数组的运行方式首先,我们先来研究一下DP数组的运行方式
(以01背包为例,哦,万恶的01背包 )(以01背包为例,哦,万恶的01背包
在这里插入图片描述
我们就可以知道,其实DP的运行,只和两行有关,一行是上一次的,一行是本次的,你知道怎么做了吗?
没错,如伪代码:

#头文件
using .......

定义数组;
int dp[2][m];

int main(){

	输入;
	
	for(){
		for(){
			日常乱写DP的灵魂——转移式;
			一行做完了没?做完了:
				交换(dp第1层,dp第二层);
		}
	}

	cout<<dp[1][m];
	return 0;
}

是不是更简洁了?还没完,还有简洁的呢!

一维,m的形式:

在这里插入图片描述

忘了什么呢???
对,前面延伸绿线和红线的元素的值已经改变了,绿色箭头与红色箭头在一起也一定是错的。
那怎么办啊,没办法喽?[沮丧]
不可能,我可是兔子啊,我可是劳动人民啊,无敌种花家的劳动人民啊,所以:
正着不行反着来嘛。。。。。。
如图:
在这里插入图片描述
这就对了嘛。
小结:面度不同的背包,应该灵活解决二维压缩一维,数据大,一定要压缩,尽量都用一维的DP。
最好再次练习二维压缩一维,不妨拿以上背包练练手,可以再去看看分组背包的代码哦!

总结

背包其实不难,只要能推出转移式就很完美了,可以多练练背包,对DP的学习很有帮助呢!!!

好了,差不多了,相信你到这一定也学会了背包,如果还一知半解的话,来打我啊 (惹不起大佬),那就再刷一遍题,再读一遍题解(据说大声朗诵和题解更配哦
希望小伙伴们点个关注,下期我们再会,233333333~拜啦!!在这里插入图片描述

  • 12
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值