背包问题总结

含义

这里有 N 个物品,每个物品的体积和价值分别为 vi、wi,我们有一个容量为 V 的背包,求这个背包能装的物品最大价值是多少。(装的所有物品总体积不能超过 V)

四种最常见的背包问题:

  • 01背包 : 每件物品最多只用一次

  • 完全背包 : 每件物品有无限个

  • 多重背包 : 每件物品最多有 Si

  • 分组背包 : 物品有 N 组,每一组里有物品若干个,每一组最多只能拿一个物品

01背包

每件物品最多只用一次(要么用一次、要么不用)

f[i,j] 代表只从前 i 件物品选,并且总体积 ≤ j的所有选法的集合,其中 f[i,j] 的值是这些所有选法中价值最大的价值

下面来想想 f[i,j] 这个集合怎么划分
在这里插入图片描述
将 f[i,j] 集合分为两部分:即第 i 个物品选 0 个还是 1 个
不含 i ,说明我们需要从前 i - 1 个物品中选且总体积 ≤ j 的所有选法的集合,即不含 i 的最大值为 f[i-1,j] 。

含 i ,说明我们需要从前 i 个物品中选且含 i 且总体积 ≤ j 的所有选法的集合,直接求不好求,我们可以先将第 i 个物品去掉,那么集合最大价值就应该为 f[i-1,j-vi],再将第 i 个物品的价值加上,即含 i 的最大值为 f[i-1,j-vi] + wi

综上,f[i,j] = max(f[i-1,j] , f[i-1,j-vi] + wi)

例题: AcWing 2. 01背包问题
在这里插入图片描述

#include<iostream>
#include<cmath>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N]; //体积、价值
int f[N][N];

int main()
{
   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=0; j<=m; j++) //枚举所有体积 
     {
     	f[i][j] = f[i-1][j];
     	if(j>=v[i])  f[i][j] = max(f[i][j], f[i-1][j-v[i]] + w[i]);
	 }
	cout << f[n][m];  	
} 

二维到一维空间优化

for(int i=1; i<=n; i++)    
     for(int j=m; j>=v[i]; j--) //枚举所有体积 
        f[j] = max(f[j], f[j-v[i]] + w[i]);
	 
	cout << f[m]; 

完全背包

每件物品有无限个

01 背包是按照第 i 个物品选 0 个还是 1 个划分 f[i,j] 的,那么完全背包就可以按照第 i 个物品选多少个来划分,0/1/2/3…/k(k 个是最多能装的)
在这里插入图片描述
含第 i 个物品 k 个(k=0/1/2/3…),直接求不好求,可以先将 k 个物品 i 去掉,那么集合最大价值就应该为 f[i - 1,j - k * v[i]] ,再将第 k 个第 i 个物品的价值加上,即 f[i - 1,j - k * v[i]] + k * w[i]。

综上,f[i,j] = f[i - 1,j - k * v[i]] + k * w[i]。
在这里插入图片描述

超时代码: 三层循环,第一层循环执行 n 次,第二层循环执行 m 次,第三层循环最多执行 m 次,所以,n*m2=109,是超时的。

#include<iostream>
#include<cmath>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N]; //体积、价值
int f[N][N];

int main()
{
   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=0; j<=m; j++) //枚举所有体积 
       for(int k=0; k*v[i]<=j; k++) 
        f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + k*w[i]);
	cout << f[n][m];  	
} 

那如何去优化呢?
在这里插入图片描述
所以,f[i,j] = max(f[i - 1,j],f[i,j-v[i]]+w),这样我们本来需要枚举 k 个状态,现在只需要枚举 2 个状态即可,执行106次即可。

#include<iostream>
#include<cmath>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N]; //体积、价值
int f[N][N];

int main()
{
   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=0; j<=m; j++) //枚举所有体积 
     {
     	f[i][j] = f[i-1][j];
     	if(j>=v[i]) f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
	 }
       
	cout << f[n][m];  	
} 

二维到一维空间优化

for(int i=1; i<=n; i++)    
     for(int j=v[i]; j<=m; j++) //枚举所有体积 
     	 f[j] = max(f[j],f[j-v[i]]+w[i]);

	cout << f[m]; 

多重背包

每件物品最多有 Si
多重背包的集合划分方式和上一题完全背包(超时的那个方法)是一样的,只是 k 有限制条件了,k 需要≤ s[i]
在这里插入图片描述

#include<iostream>
#include<cmath>
using namespace std;
const int N = 110;
int n,m;
int v[N],w[N],s[N]; //体积、价值、数量
int f[N][N];

int main()
{
   cin >> n >> m;
   for(int i=1; i<=n; i++) cin >> v[i] >> w[i] >>s[i];
   
   for(int i=1; i<=n; i++)    
     for(int j=0; j<=m; j++) //枚举所有体积 
       for(int k=0; k<=s[i] && k*v[i]<=j; k++) 
       {
           f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + k*w[i]);
       }
        
	cout << f[n][m];  	
} 

当数据范围变大了,用上面的三层循环做的话肯定是会超时的,我们有一种二进制的优化方法
在这里插入图片描述
我们可以把这个 多重背包 问题转换成 01背包 问题。
假如一个物品有 s 件,那么可以把这 s 件拆分 s 份,每份 1 个,然后对所有物品都这样拆分,拆分后的每个物品最多只能用一次,那么就转换成了01背包问题。

可是,这样拆,还是会超时的,因为,每个物品最多拆成 2000 份,所有物品拆完后一共有 1000*2000=2000000 个物品,V最大是2000,这样的话,两层循环达到109,还是超时的。
我们其实不需要拆成这么多份的,只需要将 s 转换为 log2s份即可(上取整)。

假设一个物品有 7 件,那么这七件是有从 0~7 八种选法的,我们其实可以拆分成 1、2、4 (20、21、22)这样三份,这三份的一些组合就可以将这八种选法全部表示出来:
0 = 一个都不选
1 = 1
2 = 2
3 = 1 + 2
4 = 4
5 = 1 + 4
6 = 2 + 4
7 = 1 + 2 + 4

时间复杂度:NVlog2S=1000 * 2000 * 11 = 2*107

#include<iostream>
#include<cmath>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N]; //体积、价值
int f[N][N];

int main()
{
   cin >> n >> m;
   
   int cnt = 0;
   for(int i=1; i<=n; i++)
   {
      int a,b,s;
	  cin >> a >> b >> s;
	  int k = 1;	
	  while(k <= s)
	  {
	  	 cnt++;
	  	 v[cnt] = a * k;
	  	 w[cnt] = b * k;
	  	 s -= k;
	  	 k *= 2;
	  }
	  if(s > 0)
	  {
	  	cnt++;
	  	v[cnt] = a * s;
	  	w[cnt] = b * s;
	  }
   }	
   
   n = cnt;
   for(int i=1; i<=n; i++)
     for(int j=0; j<=m; j++)
     {
     	f[i][j] = f[i-1][j];
     	if(j >= v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
	 }
	 cout<<f[n][m];
} 

分组背包

物品有 N 组,每一组里有物品若干个,每一组最多只能拿一个物品
在这里插入图片描述
f[i,j] 代表从前 i 组选,且总体积不超过 j 的所有选法的集合。
集合划分:
不选这一组,即 f[i-1,j]
② 选这一组的第 k 个,即 f[i-1,j-v[i][k]+w[i][k]]

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 110;
int n,m;
int v[N][N],w[N][N],s[N]; //体积、价值
int f[N][N];

int main()
{
   cin >> n >> m;
   for(int i=1; i<=n; i++)
   {
   	 cin >> s[i];
   	 for(int j=0; j<s[i]; j++)
   	   cin >> v[i][j] >> w[i][j];
   }
   
   for(int i=1; i<=n; i++)  //枚举每一组 
     for(int j=0; j<=m; j++) //枚举所有体积 
     {
       f[i][j] = f[i-1][j]; //不选这一组 
       for(int k=0; k<s[i]; k++) //选这组:枚举所有选择 
         if(v[i][k] <= j) 
		   f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]); 	 
	 }
       
    cout << f[n][m];	   
} 

二维到一维空间优化

for(int i=1; i<=n; i++)  //枚举每一组 
     for(int j=m; j>=0; j--) //枚举所有体积 
       for(int k=0; k<s[i]; k++) //选这组:枚举所有选择 
         if(v[i][k] <= j)  f[j] = max(f[j],f[j-v[i][k]]+w[i][k]); 	 
       
    cout << f[m];
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值