动态规划之——背包DP(入门篇)

概要说明

本文只讲了01背包和完全背包,至于其他背包问题后续补充
进阶篇(多重、混合、二维费用背包):传送门
完结篇(分组背包、有依赖的背包、 背包问题求方案数以及背包问题求具体方案):传送门

01背包

模板例题

点击这里

题意概要

在这里插入图片描述

思路

01背包的模板题

首先对于背包问题,我们只有两种选择: 选或者不选 选或者不选 选或者不选
我们先设DP状态 f ( n , W ) , n 代表选取物品的数量, W 代表当前背包可容纳的重量 f(n,W),n代表选取物品的数量,W代表当前背包可容纳的重量 f(n,W)n代表选取物品的数量,W代表当前背包可容纳的重量
我们可以先从后往前枚举,在当前 f ( n , W ) f(n,W) f(n,W)状态下,不选物品,那么 f ( n , W ) = f ( n − 1 , W ) f(n,W)=f(n-1,W) f(n,W)=f(n1,W)
(解释:不选当前的物品,那么物品的总数返回前面一个状态,当前可容纳的重量不变)
选当前物品,那么 f ( n , W ) = f ( n − 1 , W − w [ i ] ) + v [ i ] f(n,W)=f(n-1,W-w[i])+v[i] f(n,W)=f(n1,Ww[i])+v[i]
(解释:选当前物品,那么物品的总数返回前面一个状态,当前可容纳的重量减去物品重量,并且加上物品的价值)
剩下依次往前枚举,直到出现 f ( 0 , W ) = 0 或者 f ( n , 0 ) = 0 结束,即没有物品可以选和可容纳重量为 0 f(0,W)=0或者f(n,0)=0结束,即没有物品可以选和可容纳重量为0 f(0,W)=0或者f(n,0)=0结束,即没有物品可以选和可容纳重量为0

以上是记忆化搜索的步骤,我们可以将上述步骤改为递归
同样分为两种状态:选或者不选

  • 不选: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]
  • 选: d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i-1][j-v[i]]+w[i] dp[i1][jv[i]]+w[i]

那么它的状态转移方程就为: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]) dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i])

接下来看代码~~

code1

void solve(){
	int n,m;
	cin >> n >> m;
	for(int i=1;i<=n;++i){
		cin >> v[i] >> w[i];
	}
	for(int i=1;i<=n;++i)
	   for(int j=1;j<=m;++j){
	   	if(j<v[i]) dp[i][j]=dp[i-1][j];//当前重量比v[i]小,那么只能不选
	   	else dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
	   }
	cout << dp[n][m] << endl;
	return ;
}

对于上述例题来说,这个代码是过不去的
why?这个代码会超出内存限制
在这里插入图片描述

那么我们就得优化代码
观察式子,我们发现当前状态 d p [ i ] [ j ] 只跟 d p [ i − 1 ] [ j ] 有关 dp[i][j]只跟dp[i-1][j]有关 dp[i][j]只跟dp[i1][j]有关,因此我们只需要保留一维,将新的状态覆盖在原状态即可
那么新的状态转移方程为: f [ l ] = m a x ( f [ l ] , f [ l − w [ i ] ] + v [ i ] ) , l 相当于上述的 j f[l]=max(f[l],f[l-w[i]]+v[i]),l相当于上述的j f[l]=max(f[l],f[lw[i]]+v[i]),l相当于上述的j
(解释:新的状态每次都会覆盖原状态,因此每次都跟自己比较大小即可)

AC代码

code2

void solve(){
	int n,W;
	cin >> n >> W;
	for(int i=1;i<=n;++i){
		cin >> w[i] >> v[i];
	}
	for(int i=1;i<=n;++i)
	   for(int l=W;l>=w[i];--l){
	   	f[l]=max(f[l],f[l-w[i]]+v[i]);
	   }
	cout << f[W] << endl;
	return ;
}

注意: l 必须从后往前遍历 l必须从后往前遍历 l必须从后往前遍历
如果我们从前往后遍历,那么每次新状态覆盖原状态时,新状态又会覆盖新状态
什么意思呢?
其实我们每次覆盖时,当前那一层是不能覆盖当前那一层的状态的,只能后面一层覆盖前面一层的状态
前往后遍历,会导致当前层覆盖当前层的状态,这其实就是完全背包(后面来讲)
而我们从后往前遍历,后面的容量不可能比原来的容量大,因此也就不会出现上面这种情况

到此,模板题讲完了,接下来我们来看一道01应用题

01背包的应用题

题目来源

Q我~~~~

思路

01背包的变形题
分别对4个科目进行DP,首先我们很容易想到:

  • 左脑和右脑所花的时间尽可能相同
  • 单纯考虑左脑所花的时间,那么就是面临两种选择:做这题 or 不做这题
  • 所花的时间为总时间减去左脑所用的时间

首先定义总重W,W的值为单科所花的总时间的一半(尽可能让左右脑所花时间相同)
写出状态状态转移方程 f [ k ] = m a x ( f [ k ] , f [ k − b [ j ] ] + b [ j ] ) ; f[k]=max(f[k],f[k-b[j]]+b[j]); f[k]=max(f[k],f[kb[j]]+b[j]);,与01背包不同的是,这题的重量和价值是相同的
算出左脑最多花的时间,ans加上总时间减去 f [ s u m / 2 ] f[sum/2] f[sum/2]即单科所花的时间(sum为总时间)

code

void solve(){
	for(int i=1;i<=4;++i) cin >> a[i];
	for(int i=1;i<=4;++i){
		int sum=0;
		for(int j=1;j<=a[i];++j){
			cin >> b[j];
			sum+=b[j];
		}
		for(int j=1;j<=a[i];++j)
		   for(int k=sum/2;k>=b[j];--k){
		   	f[k]=max(f[k],f[k-b[j]]+b[j]);
		   }
		ans+=sum-f[sum/2];
		for(int j=1;j<=sum/2;++j) f[j]=0;
	}
	cout << ans << endl;
	return ;
}

完全背包

模板例题

点这里~~

题意概要

在这里插入图片描述

思路

完全背包的状态转移方程和01背包是一模一样的,都为: f [ l ] = m a x ( f [ l ] , f [ l − w [ i ] ] + v [ i ] ) ; f[l]=max(f[l],f[l-w[i]]+v[i]); f[l]=max(f[l],f[lw[i]]+v[i]);
区别就在于 l l l是从前往后遍历
例如:背包容量为10 有一个物品重量为1 价值为1
从前往后遍历, f [ 1 ] = m a x ( f [ 1 ] , f [ 1 − w [ 1 ] ] + v [ 1 ] ) = 1 f [ 2 ] = m a x ( f [ 2 ] , f [ 2 − w [ 1 ] ] + v [ 1 ] ) = 2 ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ f[1]=max(f[1],f[1-w[1]]+v[1])=1 f[2]=max(f[2],f[2-w[1]]+v[1])=2······· f[1]=max(f[1],f[1w[1]]+v[1])=1f[2]=max(f[2],f[2w[1]]+v[1])=2⋅⋅⋅⋅⋅⋅⋅
最终 f [ 10 ] = 10 f[10]=10 f[10]=10
相当于每次遍历都是在原来覆盖过的状态进行下一轮的覆盖

接下来看代码

code

void solve(){
	int W,n;
	cin >> W >> n;
	for(int i=1;i<=n;++i){
		cin >> w[i] >> v[i];
	}
	for(int i=1;i<=n;++i)
	   for(int l=w[i];l<=W;++l){
	   	f[l]=max(f[l],f[l-w[i]]+v[i]);
	   }
	cout << f[W];
	return ;
}
  • 46
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值