【动态规划】背包问题总结:01、完全、多重与其二进制优化、分组背包 题解与模板

来自AcWing算法基础课。
在这里插入图片描述

AcWing 2.01背包问题

AcWing 2. 01背包问题
写得贼好的题解:AcWing 2. 01背包问题(状态转移方程讲解)
我的打卡
二维的背包代码:

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e3+10;
int n,m;//物品数,背包容量 
int v[N],w[N];
int dp[N][N];//dp[i][j]表示拿了前i个东西,背包容量为j时的最大价值 
int main()
{
	cin>>n>>m;
	fir(i,1,n) cin>>w[i]>>v[i];
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			if(j<w[i]) dp[i][j]=dp[i-1][j];
			else dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
		}
	cout<<dp[n][m];
	return 0;
}

关于想要变成一维:来自上面的题解

将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形

为什么可以这样变形呢?我们定义的状态f[i][j]可以求得任意合法的i与j最优解,但题目只需要求得最终状态f[n][m],因此我们只需要一维的空间来更新状态。

(1)状态f[j]定义:N 件物品,背包容量j下的最优解

(2)注意枚举背包容量j必须从m开始

(3)为什么一维情况下枚举背包容量需要逆序?在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i -1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。

(4)例如,一维状态第i轮对体积为 3 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。

(5)简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i]) 。
作者:深蓝
链接:https://www.acwing.com/solution/content/1374/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

一维代码:

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e3+10;
int n,m;//物品数,背包容量 
int v[N],w[N];
int dp[N];//dp[i]表示拿了前N个东西,背包容量为i时的最大价值 
int main()
{
	cin>>n>>m;
	fir(i,1,n) cin>>w[i]>>v[i];
	
	for(int i=1;i<=n;i++)
		//如果要进行状态转移,那么背包容量要大于物品体积,不然不会发生改变 
		for(int j=m;j>=w[i];j--)
		{
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//j容量,i物品 
		}
	cout<<dp[m];
	return 0;
}

AcWing 3. 完全背包问题


写的贼好的题解
我的打卡

二维:

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e3+10;
int n,m;//物品数,背包容量 
int v[N],w[N];
int dp[N][N];//dp[i][j]表示拿了前i个东西,背包容量为i时的最大价值 
int main()
{
    cin>>n>>m;
    fir(i,1,n) cin>>w[i]>>v[i];

    for(int i=1;i<=n;i++)       
        for(int j=0;j<=m;j++)//大的状态由小的状态得来,所以循环要从小到大 
        {
            dp[i][j]=dp[i-1][j];
            if(j>=w[i]) dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+v[i]);
        }
    cout<<dp[n][m];
    return 0;
}

一维:

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e3+10;
int n,m;//物品数,背包容量 
int v[N],w[N];
int dp[N];//dp[i]表示拿了前N个东西,背包容量为i时的最大价值 
int main()
{
	cin>>n>>m;
	fir(i,1,n) cin>>w[i]>>v[i];
	
	for(int i=1;i<=n;i++)
		//如果要进行状态转移,那么背包容量要大于物品体积,不然不会发生改变 
		for(int j=w[i];j<=m;j++)
		{
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//j容量,i物品 
		}
	cout<<dp[m];
	return 0;
}

可以看到,完全背包和01背包的代码和空间优化方法十分类似。
不同在于:
01背包的一维中,j是从m到w[i]递减的,完全背包的j是从w[i]到m递增的。 原因是:(来自上述题解的评论区)

01背包更新f[i][j] 用的是 f[i-1][j - v] 和 f[i-1][j],完全背包更新用的是f[i][j - v] 和 f[i- 1]f[j],在一维中 f[i-1][j] 就是 f[j], 而01背包的f[i-1][j-v]是i-1层 , 更新的时候第i层下标大的用第i-1层下标小的 ,需要逆序枚举,完全背包的f[i][j-v] 就是用的第i层,更新的时候第i层下标大的用第i层下标小的,需要正序枚举。

我的理解:
对于从之前的更新到现在的,完全背包是max(从之前的不拿,到现在的狂拿),01背包是max(从之前的不拿,从之前的拿)。

对于完全背包来说,要状态转移是从本层状态再拿东西(即物品i不变,对物品i狂拿),即f[i][j]由f[i][j-w]更新而来,即由本层更新过来;
而01背包的一维中,都从上一层状态转移过来(f[i][j]由f[i-1][j],f[i-1][j-v]更新而来),即由i-1层状态转移过来。

完全背包从本层更新,而每个本层是从小到大累计的,所以要顺序;
01背包由上一层更新,因此每一层更新都要保证上一层是“原始的”,所以要逆序。

AcWing 4. 多重背包问题

AcWing 4. 多重背包问题
打卡

如果空间允许的话,把多重背包问题拆成01背包问题

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e5+10;
int n,m;
int dp[N];//dp[i]表示背包容量为i时的最大价值
int v[N],w[N]; 
int idx1,idx2;
int main()
{
	cin>>n>>m;
	fir(i,1,n)
	{
		int a,b,c;cin>>a>>b>>c;
		while(c--)
		{
			w[idx1++]=a;v[idx2++]=b;
		}
	}
	
	for(int i=0;i<idx1;i++)
		for(int j=m;j>=w[i];j--)		
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		
	cout<<dp[m];
	return 0;
}

AcWing 5. 多重背包问题 II

AcWing 5. 多重背包问题 II
参考题解1:AcWing 5. 二进制优化,它为什么正确,为什么合理,凭什么可以这样分??
参考题解2:AcWing 5. 多重背包问题 II

二进制优化多重背包问题代码:

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e5+10,M=2e3+100;
int n,m;
int w[N],v[N];//逐一枚举最大是N*logS
int dp[M];
int cnt;//组别 
int main()
{
	cin>>n>>m;
	fir(i,1,n)
	{
		int a,b,c;cin>>a>>b>>c;
		int k=1;
		while(k<=c)
		{
			cnt++;
			w[cnt]=a*k;
			v[cnt]=b*k;
			c-=k;
			k*=2;
		} 
		if(c)
		{
			cnt++;
			w[cnt]=a*c;
			v[cnt]=b*c;
		}
	}
	
	n=cnt;
	for(int i=1;i<=n;i++)
		for(int j=m;j>=w[i];j--)
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
	
	cout<<dp[m];
	return 0;
}

AcWing 9. 分组背包问题

AcWing 9. 分组背包问题
参考题解:AcWing 9. 分组背包问题
我的打卡
二维:

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e2+10;
int n,m;
int s[N];//组号
int dp[N][N],w[N][N],v[N][N];//第i组的第j个 
int main()
{
	cin>>n>>m;
	fir(i,1,n)
	{
		cin>>s[i];
		for(int j=1;j<=s[i];j++)
			cin>>w[i][j]>>v[i][j];
	}
	
	for(int i=1;i<=n;i++)
		for(int j=0;j<=m;j++)
		{
			dp[i][j]=dp[i-1][j];//先不选
			//一组里只能选一个 
			for(int k=1;k<=s[i];k++)
			{
				if(j>=w[i][k]) dp[i][j]=max(dp[i][j],dp[i-1][j-w[i][k]]+v[i][k]);
			} 
		}
	cout<<dp[n][m];
	return 0;
}

一维优化:

#include<bits/stdc++.h>
using namespace std;
#define fir(i,a,n) for(int i=a;i<=n;i++)
const int N=1e2+10;
int n,m;
int s[N];//组号
int dp[N],w[N][N],v[N][N];//第i组的第j个 
int main()
{
	cin>>n>>m;
	fir(i,1,n)
	{
		cin>>s[i];
		for(int j=1;j<=s[i];j++)
			cin>>w[i][j]>>v[i][j];
	}
	
	for(int i=1;i<=n;i++)
		for(int j=m;j>=0;j--)
		{			
			//一组里只能选一个 
			for(int k=1;k<=s[i];k++)
			{
				if(j>=w[i][k]) dp[j]=max(dp[j],dp[j-w[i][k]]+v[i][k]);
			} 
		}
	cout<<dp[m];
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

karshey

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

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

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

打赏作者

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

抵扣说明:

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

余额充值