九大类背包问题专题--合辑篇(详解)

1. 01背包问题

问题:
有N件物品和一个容量是V的背包。
第i件物品的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值最大。

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品数量和背包容积。
接下来有N行,每行两个整数vi,wi,用空格隔开,分别表示第i件物品的体积和价值。

输出格式
输出一个整数,表示最大价值

数据范围
0<N.V<=1000
0<vi,wi<=1000

输入样例
4 5
1 2
2 4
3 4
4 5

输出样例
8

分析思路:

二维动态规划
f[i][j]:只看前i个物品,当前使用所有的体积是j的情况下,最价值最大是多少

结果:
f[n][0-v],从0到v枚举,取最大值,考虑n件物品,体积从0,1,2,3...V,挑选最大的,
result={max[n][0-v]}

f[i][j]  j假设当前第i-1个物品都已经算完了,现在考虑的物品是i,有两种选择:
1.不选第i个,问题就变为只考虑前i-1个物品,体积为j的情况下最大价值
f[i][j]=f[i-1][j]
2.选第i个物品,需要从j里面把第i个物品的体积先去掉,则当前剩余体积为j-v[i]
f[i-1][j-v[i]]

答案:
f[i][j]=max{1,2}

初始化
f[0][0]=0; 一个物品都不考虑的情况下,体积只能为0

时间复杂度:
O(n*n)=10^6

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=1010;  //物品个数
int n,m;  //n:物品个数,m:总容量
int f[N][N];
int v[N],w[N];  //每个物品,及其价值
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) //从1开始
	cin>>v[i]>>w[i];
	for(int i=1;i<=n;i++)
	 for(int j=m;j>=v[i];j--){ //所有体积,从前往后枚举
	 f[i][j]=f[i-1][j]; //选的话,从f[i-1]
	    if(j>=v[i]) //判断,j<第i个物品的体积,无法选
	   f[j]=max(f[j],f[[i-1][j-v[i]]+w[i]);
	   }
	   int res=0;
	   for(int i=0;i<=m;i++)
	   res=max(res,f[i]);
	   cout<<res<<endl;
	   return 0;
} 

优化:

1.二维数组变为一维数组
每次计算的时候只和前一层有关,不需要把所有的都记录
一维数组
f[i]表示体积是i的情况下,最大价值是多少
压缩一维
改变循环顺序,把第一种选择f[i-1]去掉,从小到大枚举
算f[j]时,用f[j-v[i]],算第二种选择的时候尽量保证f[j-v[i]]之前没有算过,所以算第一种的时候用的是f[i][j-v[i]]
算f[j]时,f[j-v[i]]就已经算过了。

怎样保证算f[j]时,f[j-v[i]]这个状态是f[i-1]的状态,而不是f[i]的.->从大到小枚举

在用f[j-v[i]]这个状态时,f[j-v[i]]一定小于j,所以它一定没有算过。它就一定是f[i]的状态。

2.把枚举从0-m循环去掉
初始化并不是只把f[0]初始化为0,初始化的时候把所有的f[i]都变为0.
f[m]表示所有体积小于等于m的情况下,最大价值,而并不是体积恰好等于m的情况下最大价值

假设:最优选法,总体积是k
k<m的话,f[k]就是最大价格,
k是从f[0]转出来的

若求体积恰好是m的情况下,最大价值
在初始化的时候
f[0]=0;
f[i]=负无穷;
确保所有状态从f[0]转过来

优化代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=1010;
int n,m; 
int f[N];
int v[N],w[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=m;j>=v[i];j--)
	   f[j]=max(f[j],f[j-v[i]]+w[i]);
	   //初始化的时候把所有的f[i]都变为0
	   //原来:f[0]=0;
	   //现在: f[i]=0  
	   cout<<f[m]<<endl;
	   return 0;
} 

在这里插入图片描述


2.完全背包问题

和01背包问题的区别:
01背包问题:1件物品只能选或者不选

完全背包问题:1件物品可以重复选多次,只要不超过总体积

题目:
问题:
有N件物品和一个容量是V的背包。
第i件物品的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值最大。

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品数量和背包容积。
接下来有N行,每行两个整数vi,wi,用空格隔开,分别表示第i件物品的体积和价值。

输出格式
输出一个整数,表示最大价值

数据范围
0<N.V<=1000
0<vi,wi<=1000

输入样例
4 5
1 2
2 4
3 4
4 5

输出样例
10

分析思路:

f[i]:表示总体积是i的情况下,最大价值是多少

f[0…m]枚举,m表示背包容量
result=max{f[0…m]}

01背包从大到小枚举->保证每个物品只用1次

从前往后考虑
for(int i=0;i<n;i++){
    for(int j=m,j>=v[i];j--)
       f[j]=max(f[j],f[j-v[i]]+w[i])
原因:保证状态转移时,算f[j]时,算的是f[i][j](第i个物品的j),保证转移时,用的是f[i-1][j]
f[j]=max(f[j],f[i-1][j-v[i]]+w[i]);
而不是 f[j]=max(f[j,]f[i][j-v[i]]+w[i])
从大到小枚举,v[i]>0,[j-v[i]]一定没有被算过,则它一定是f[i-1]的状态。

完全背包从小到大枚举->保证在背包容量允许的条件下,每个物品可重复多次使用


现在枚举第i个物品,体积从大到小枚举
 for(int j=m,j>=v[i];j--)
   for(int k=0;k*v[i]<=j;k++)  //选的物品数
      f[j]=max(f[j],f[j-k*v[i]]+k*w[i])  //一个物品重复选多次,直到装不下,一定不包含当前的第i个物品
算f[j]时,用来转移的状态一定是f[i-1]的

算f[j]时,所有比j小的f[j]都没有被算过,上面所有的f[j-k*v[i]都没算过,都不包含第i个物品

枚举选k个,对不同的k,更新刚才的状态,体积就是j-k*v[i]],价值就是k*w[i]

从小到大枚举,在算f[j]时,用j-v[i]这个状态,j-v[i]这个状态是被算过的,
因为从小到大枚举,所以算f[j]时,比j小的都已经算过了,j-v[i]也在里面,所以j-v[i]这个状态也被算过了

f[j-v[i]]表示考虑前i个物品,包括第i个物品的情况下,体积是j-v[i]时,最大价值是多少,可能已包含若干的当前第i个的物品。

综上,从小到大更新状态,f[j-v[i]]包含第i个物品,证明从小到大枚举能枚举到最优解.

证明:数学归纳法(迭代,递推关系)

1.假设前i-1个物品考虑完之后,所有的f[j]都是符合的,所有f[j]表示体积是j的情况下,它的最大价值就是f[j]
初始化:f[0][0]正确

2.考虑完第i个物品后,所有f[j]也是符合的

对某一个j而言,把j确定,最优解里面包含k个v[i],第i个物品

从小到大枚举,一定可以枚举到f[j-k*v[i]]状态,算此状态时,
用f[j-k*v[i]-v[i]]+w[i]更新状态(此最优解不包含v[i]),

反之f[j-k*v[i]]不包含,所以不会更新,取完max后一定是原来的数
f[j-k*v[i]]此状态已算完

接着算(k-1)*v[i]状态:f[j-(k-1)*v[i]]会用f[j-v[i]]+w[i])更新它,更新之后变为:f[j-k*v[i]-v[i]]+w[i]

综上:f[j-(k-1)*v[i]]这个状态会用f[j-k*v[i]](一个v[i]都不包含)这个状态更新;枚举完一个之后就会包含一个v[i],
依次类推:算f[j]时,用f[j-v[i]]+w[i])(此状态一定计算过只包含k-1个物品这个状态)更新,所以说f[j]这个物品枚举了包含k个v[i]

如果最后结果中包含k个v[i],那么f[j]就一定枚举到过这种状态
所以f[j]就一定会枚举到最优解

举例:
1、前1个物品中:f[1]=2,f[2]=4,f[3]=6,f[4]=8,f[5]=10,显然f[j]都是正确的。
2、假设在前i-1个物品中,f[j]都是正确的。
3、在前i个物品中,对于某个j而言,如果最优解包含k个v[i],则一定会枚举到f[j-kv[i]],f[j-kv[i]]是如何得到的呢?
f[j-kv[i]=max{f[j-kv[i]],f[j-kv[i]-v[i]]+w[i]} (由于j正序,f[j-kv[i]]为前i-1个物品的值,f[j-k*v[i]-v[i]]为前i个物品的值).
最后会传递到max{f[v[i]],f[0]+w[i]}处。

因为f[v[i]]一定正确(2中已假设前i-1个物品中的f[j]全都正确),f[0]一定正确=0,w[i]一定正确,所max{f[v[i]],f[0]+w[i]}
一定正确=>f[j-k*v[i]]一定正确=>f[j]一定正确.

时间复杂度:1000000
代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=1010;
int n,m; 
int f[N];
int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++){  //从0开始枚举 
		int v,w;
		cin>>v>>w;
		for(int j=v;j<=m;j++) //小到大枚举体积,<v的不枚举 
		  f[j]=max(f[j],f[j-v]+w);
	} 
	/*初始化的时候把所有的 f[i]都初始化为0 
	/f[m] 表示体积 小于等于m的情况下 ,所有方法里面 
	/转移的时候不一定是从f[0]转移过来的,可以从任意一个状态转移 
	/当用不完整个背包的容量的时候 ,假设剩k容量就可以从k转移 
	/则一定可以枚举到 最优解一样的选法 ,因为体积变成了一样的 
	/假设最优解用了m-k的体积,f[m]就一定可以从k开始枚举 ,也只用了 m-k的体积
	 也会枚举到最优解 ,所以f[m]表示的就是体积小于等于 m的情况下的最优解 */ 
	cout<<f[m]<<endl;  //最大价值(不需要枚举从0到m) 
	return 0;
}

在这里插入图片描述
若题目问体积恰好为m的情况下,最大价值为多少?
除f[0]=0
在初始化时所有f[i]初始化为负无穷


3.多重背包问题1

题目:
有N件物品和一个容量是V的背包。
第i种物品最多有si件,每件的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有N行,每行三个整数vi,wi,si,用空格隔开,分别表示第i种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值

数据范围
0<N.V<=100
0<vi,wi,si<=100

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例
10

分析思路:

时间复杂度O(n^3)

f[i]:表示总体积是i的情况下,最大价值是多少

每件物品选法,假设有s个,则选法有s+1种选法。可以选0,1,2,3…m个。

01背包:

从前往后考虑
for(int i=0;i<n;i++){
    for(int j=m,j>=v[i];j--)
       f[j]=max(f[j],f[j-v[i]]+w[i])

选的话为f[j-v[i]],不选的话为f[j]不变

多重背包
可以看做01背包的扩展

状态转移加一种循环

选法有很多种,0,1,2,…s

f[j]=max(f[j],f[j-v[i]]+w[i],f[j-2*v[i]]+w[i]...);

第一种情况,在初始化的时候,把所有f[i]都初始化为0,答案就是f[m]
f[i]=0;

第二种情况:在初始化时把f[0]=0,其余f[i]都初始化为负无穷,枚举0~m求最大值。
f[0]=0;
f[i]=-INF;(i!=0)
max{f[0....m]}

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

const int N=110;
int n,m; 
int f[N];
int main(){
	cin>>n>>m;
	for(int i=1;i<n;i++){  //枚举所有物品 
	int v,w,s; 
	cin>>v>>w>>s;
	 for(int j=m;j>=0;j--)  // 从大到小枚举 
	    for(int k=1;k<=s&&k*v<=j;k++)  //状态转移,s+1种选法,判断枚举个数不能大于j 
	   f[j]=max(f[j],f[j-k*v]+k*w);  
	   cout<<f[m]<<endl;
}
	   return 0;
} 

在这里插入图片描述


二进制优化方法

多重背包问题2

题目:
问题:
有N件物品和一个容量是V的背包。
第i种物品最多有si件,每件的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有N行,每行三个整数vi,wi,si,用空格隔开,分别表示第i种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值

数据范围
0<N<=1000
0<V<=1000
0<vi,wi,si<=2000

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例
10

分析思路:

把多重背包转化为01背包
假设v,w,s
假设物品有s个,把物品拆成s份(重复s份),转化后变为01物品,每个物品最多只能用一次,
其复杂度很高 10^9

二进制优化方法

假设质量为,用最少的物品的个数情况下表示出来
每个数两种选法,把0~7八种方案表示出来,至少需要3个数log以2为底8的对数,
乘法原理:8种方案,选3个数,2^3=8
下界一定为3,往上取整,注意3个数能不能满足要求
0
1
3=1+2
4=4
5=1+4
6=2+4
7=1+2+4

给定任意数s,最少把s可以分成多少个数(划分的数,两种选择选或者不选)拼成<=s.
log2(s)往上取整
但是遇到不是2的整数幂,无法算出整数。

举例:10
log2(10)上取整,4
1 2 4 8
(这样取的话不止可以表示0~10,还可以表示0 ~15,不能选,一共只有10个物品)
让s一直减,先减1再减2,减4,减8,一直减到负数为止,最后剩下的数直接放入
上式变为:
1 2 4 3(1+2+4=7,剩余3直接放入)
这4个数就一定可以表示0~10,
原因:1-4可以表示0~7,
3和0~7可以表示0 ~10

常规情况:
s-1 -2 -4 -8
减到为负为止,把剩余数,直接取出来,这个数和前面2的整数次幂组成的状态可以凑出0~s,这样对每个s,分成log(s)份

时间复杂度:2*10^7

把每个物品拆成log份,1个,2个,4个…(2的整数幂)

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

const int N=110;
int n,m; //n个物品,m总容量 
int f[N];
struct good
{
	int v,w; //体积,价值 
 };
 int main(){
 	vector<good>goods; //表示物品 
 	cin>>n>>m;
 	for(int i=0;i<n;i++){  //枚举所有物品 
	int v,w,s; //体积,价值,个数 
	cin>>v>>w>>s;
	for(int k=1;k<=s;k*=2) //2的整数幂 
	{
		s-=k; //s一直减,直到为负数
		goods.push_back({v*s,w*s}); //把拆完后的物品放到物品组 
	}
	if(s>0) goods.push_back({v*s,w*s}); //s有剩余,剩余的物品加入物品组 
}
   //01背包,从大到小,枚举所有 
	for(auto good: goods) //遍历vector,good
	   for(int j=m;j>=good.v;j--)
	      f[j]=max(f[j],f[j-good.v]+good.w);
		  cout<<f[m]<<endl; 
		  return 0;
 }

多重背包问题3—究极版

题目:
问题:
有N件物品和一个容量是V的背包。
第i种物品最多有si件,每件的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有N行,每行三个整数vi,wi,si,用空格隔开,分别表示第i种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值

数据范围
0<N<=1000
0<V<=20000
0<vi,wi,si<=20000

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例
10

分析思路:

时间复杂度:
按上一道题时间复杂度算
1000*log(20000)20000=310^8
超出时限上限

单调队列问题
考虑算f[j],枚举一共有多少个第i个物品,以此类推,看k个数,在所有和j归在一类余数相同的体积中
也就是说算j的时候,看前k-1个数中挑最大值
f[j]=f[j-v]+w,f[j-2v]+2w,…f[j-kv]+kw

算f[j+v]每个数都会变化,每个数都会+w
f[j+v]-f[j]+w,f[j+v]+2w
因为求最大值,所有数都+w,并不影响其他数之间的关系,只需求哪个数最大
可把f[j]变为越小的加的越大
f[j]变为f[j]-k
w,余数是j模v的第k个数
第一个数减0w,f[0]=0
f[v]变为:f[v]-1
w
f[2v]-2*w
以此类推,在比较的时候,越小(前)的数,加上之后会变得幅度比后面的数都大

算f[j]+v时,把k个数往后移1位,但总容量中仍然有k个数,变为经典单调队列问题

给定一个序列,动态求所有长度为k的背包里面的最大值

举例:
假设一共一串数,先求前3个数的最大值,再求下一个3个数的最大值。
在这里插入图片描述

多重背包单调队列优化,需要理解两点

1.容量为什么分类(分完类,同一类里的容量最大价值就可以用单调队列了)

2.怎么用单调队列处理。

#include <iostream>
using namespace std;

const int N=20010;
int n,m;
int f[N],g[N],q[N];
int main()
{
cin>>n>>m;
for(int i=0;i<n;i++){   //物品种类枚举O(n)
int v,w,s;
cin>>v>>w>>s;
for(int j=0;j<v;j++){  //容量类别枚举O(v)
int le=0,ri=-1;
for(int k=j;k<=m;k+=v){// O(每个类别里容量的个数),
//为什么正序,因为单调队列里保存了q[N]容量最大价值的大小关系
//以下为单调队列的典型写法,q保存区间大小关系,g保存下标(这里是容量)用来判断s个物品最大的价值
while(le<=ri&&q[ri]<f[k]-k/v*w) ri--;
ri++;
q[ri]=f[k]-k/v*w;
id[ri]=k;
if(g[le]+s*v<k) le++;
f[k]=q[le]+g[ri]/v*w;
}
}
}
cout<<f[m]<<endl;
return 0;
}

代码:

#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;

const int N=20010;
int n,m; 
int f[N],g[N],q[N];
int main(){
	cin>>n>>m;
	for(int i=1;i<n;i++){  //枚举所有物品 
	int v,w,s; 
	cin>>v>>w>>s;
	memcpy(g,f,sizeof f);
	for(int j=0;j<v;j++){  //从0~c枚举余数 
	//余数可单独考虑 
		int hh=0,tt=-1;
		for(int k=j;k<=m;k+=v)  //枚举余数j里面的所有数
		{  //单调队列优化
			f[k]=g[k];
			if(hh<=tt&&k-s*v>q[hh])  //每次把队首取出来,它是最大的
			hh++;
			if(hh<=tt)
			f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v*w); //用最大数更新当前数
			while(hh<=tt && g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w)  //把当前数插入队列,把队列中不用元素剔除
			 tt--;
			 q[++tt]=k;把当前数加入队列
		}
	}
}
    cout<<f[m]<<endl;
	return 0;
}
	

在这里插入图片描述


4.混合背包问题

问题:
有N件物品和一个容量是V的背包。

物品一共有三类:
第一类:物品只能用1次(01背包)
第二类:物品可以用无限多次(完全背包)
第三类:物品最多只能用si次(多重背包)

每种的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品种数和背包容积。
接下来有N行,每行三个整数vi,wi,si,用空格隔开,分别表示第i种物品的体积、价值和数量。

si=-1:表示第i种物品只能用1次
si=0:表示第i种物品可以用无限次
si>0:表示第i种物品可以使用si次

输出格式
输出一个整数,表示最大价值

数据范围
0<N,V<=1000
0<vi,wi<=1000
-1<=si<=1000

输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2

输出样例
8

分析思路:

分成若干份
判断当前物品是哪一类的,转移相应的类
01背包:从大到小枚举
完全背包:从小到大枚举

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

const int N=1010;
int n,m; //n个物品,m总容量 
int f[N];
struct Good 
{
	int kind;
	int v,w; //体积,价值 
 };
 vector<Good>goods; //表示物品
 int main(){
 	cin>>n>>m;
 	for(int i=0;i<n;i++)  //枚举所有物品 
	 {  
	int v,w,s; //体积,价值,个数 
	cin>>v>>w>>s;
	if(s<0)goods.push_back({-1,v,w});  //01背包 
	else if(s==0)  goods.push_back({-1,v,w});  //完全背包,输入信息 
	else
	{  //多重背包问题 
		for(int k=1;k<=s;k*=2)  //枚举分成哪些份 
		{
			s-=k;
			goods.push_back({-1,v*k,w*k});  //把k份物品直接放入 ,类别是01背包 
		}
		if(s>0)goods.push_back({-1,v*s,w*s});
	}
}
for(auto good:goods) //遍历所有物品 
{
	if(good.kind<0)  //01背包 
	{
		for(int j=m;j>=good.v;j--) //从大到小枚举 
		f[j]=max(f[j],f[j-good.v]+good.w);
	}
	else
	{
		for(int j=thing.v;j<=m;j++)  //完全背包,从小到大枚举 
		f[j]=max(f[j],f[j-good.v]+good.w);
	}
}
cout<<f[m]<<endl;
return 0;
}

5.二维费用的背包问题

问题:
有N件物品和一个容量是V的背包,背包能承受的最大重量是M。

每件物品只能用一次,体积是vi,重量是mi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,总重量不可超过背包可承受的最大重量,且价值总和最大。
输出最大价值

输入格式
第一行有两个整数,N,V,M用空格隔开,分别表示物品种数、背包容积和背包能承受的最大重量。

接下来有N行,每行三个整数vi,mi,si,用空格隔开,分别表示第i种物品的体积、价值和数量。

输出格式
输出一个整数,表示最大价值

数据范围
0<N<=1000
0<V,M<=100
0<vi,mi<=100
0<wi<=1000

输入样例
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6

输出样例
8

分析思路
时间复杂度:10^7

每个物品只能用一次->01背包问题

枚举:体积,重量->从大到小枚举

f[i][j]:表示总体积是i,重量为j的情况下,最大价值是多少
状态转移:
第一层循环:枚举每个物品(从前往后)
第二层循环:枚举体积
第三层循环:枚举重量

代码:

#include<iostream>
#include<algorithm>
using namespace std;

const int N=110;
int n,v,m; 
int f[N][N];

int main(){
	cin>>n>>v>>m;
	for(int i=0;i<n;i++)  //从前往后枚举物品 	
	{
	int a,b,c; 
	cin>>a>>b>>c;
	 for(int j=v;j>=a;j--) //体积从大到小枚举
	    for(int k=m;k>=b;k--) //重量从大到小枚举 
	    f[j][k]=max(f[j][k],f[j-a][k-b]+c); //转移 
	    cout<<f[v][m]<<endl;
	    return 0;
} 
} 


6.分组背包问题

问题:
有N组物品和一个容量是V的背包。

第组物品有若干个,同一组内的物品最多只能选一个每件物品的体积是vij,价值是wij。其中i是组号,j是组内编号。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。
输出最大价值

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品种数和背包容积。

接下来有N组数据:
1.每组数据第一行有一个整数Si,表示第i个物品组的物品数量;
每组数据接下来有Si行,每行有两个整数vij,wij,用空格隔开,分别表示第i个物品组的第j个物品体积和价值

输出格式
输出一个整数,表示最大价值

数据范围
0<N.V<=100
0<Si<=100
0<vij,wij<=100

输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例
8

分析思路:

f[i][j]:表示总体积是i,重量为j的情况下,最大价值是多少
分组背包枚举决策:s+1种
选择0种,第1个,第2个,一共s+1种选择

第一层:枚举物品组;
for(int i=0;i<n;i++)
第二层:枚举体积(每个物品只能用一次)从大到小枚举
for(int j=m;j>=v;j--)
第一种决策:都不选,
第二种决策:选第一个,...依次类推
f[j]=max{f[j],f[j-v[0]]+w[0],f[j-v[1]]+w[1],...,f[j-v[s-1]]+w[s-1]};

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=110;
int n,m; 
int f[N],v[N],w[N];  //个数,体积,重量 

int main(){
	cin>>n>>m;
	for(int i=0;i<n;i++)  //从前往后枚举物品 	
	{
		int s;
		cin>>s;
		for(int j=0;j<s;j++)  //输入所有物品 
		cin>>v[j]>>w[j];
		for(int j=m;j>=0;j--) //体积从大到小枚举 
		  for(int k=0;k<s;k++) //s种决策,枚举 
		     if(j>=v[k])  //判断,保证v[k]>0 
		       f[j]=max(f[j],f[j-v[k]]+w[k]);
	}
	cout<<f[m]<<endl;
	return 0;
}

7.背包问题求方案数

问题:
有N件物品和一个容量是V的背包。

每件物品只能用一次,第i件物品的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。

输出最优选法方案数。注意答案可能很大,请输出答案模10^9+7的结果

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品数量、背包容积。

接下来有N行,每行两个个整数vi,wi,用空格隔开,分别表示第i件物品的体积、价值。

输出格式
输出一个整数,表示方案数模10^9的结果。

数据范围
0<N,V<=1000
0<V,M<=1000

输入样例
4 5
1 2
2 4
3 4
4 6

输出样例
2

分析思路:

f[i]:体积是恰好j的情况下,最大价值是多少
g[i]:体积是j的情况下, 方案数是多少

第一种决策,最大值和最优解一样
先算选和不选两种方案最大价值是多少,看到底从哪个决策转移过来
假设其中一个比另外一个大 ,只能从其中一种决策转移过来 (选择决策的方案数)
若两种决策答案一样 ,把两种决策方案数都要选择
更新的时候同时需要更新g数组

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1010,mod=1000000009,INF=1000000;
int n,m;
int f[N],g[N]; //g[i]体积是j的情况下 方案数是多少 
int main(){  //f[j]体积恰好是j的情况下 
	cin>>n>>m;
	g[0]=1; 
	//体积是0的方案数只有1种 
	for(int i=1;i<=m;i++) f[i]=-INF;  //f[0]=0,除此之外初始状态记为负无穷,所有状态从0开始更新 
	for(int i=0;i<n;i++)  //枚举物品 
	{
		int v,w;
		cin>>v>>w;
		for(int j=m;j>=v;j--) //从大到小枚举体积 
		{
			int t=max(f[j],f[j-v]+w); //第一种决策,最大值和最优解一样 
			int s=0;
			if(t==f[j]) s+=g[j]; //两种都选择
			if(t==f[j-v]+w) s+=g[j-v]; //第二种决策,最大值和最优解一样,两种都选择
			if(s>=mod) s-=mod;  //答案和大于10^9,减去这个数 
			f[j]=t;  //记录最优解 
			g[j]=s; //记录方案数 
		}
	}
	//统计最优解方案数,遍历整个数组,最优解不一定是f,不一定要用满f的体积才能得到 
	int maxw=0;
	for(int i=0;i<=m;i++) maxw=max(maxw,f[i]);
	int res=0;
	for(int i=0;i<=m;i++) //所有等于最优解的方案 
	   if(maxw==f[i])
	   {
	   	res+=g[i];  //加上体积是i的方案数 
	   	if(res>=mod)
	   	res-=mod;
	   }
	   cout<<res<<endl;
	   return 0; 
}

8.背包问题求具体方案数

问题:
有N件物品和一个容量是V的背包。

每件物品只能用一次,第i件物品的体积是vi,价值是wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。

输出字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号的序列范围是1…N.

输入格式
第一行有两个整数,N,V用空格隔开,分别表示物品数量、背包容积。

接下来有N行,每行两个个整数vi,wi,用空格隔开,分别表示第i件物品的体积、价值。

输出格式
输出一行,包含若干个用空格隔开的整数吗,表示最优解中所选物品的编号序列,且该编号序列的字典序最小。
物品编号范围1…N.
数据范围
0<N,V<=1000
0<vi,wi<=1000

输入样例
4 5
1 2
2 4
3 4
4 6

输出样例
1 4

分析思路:

f[i][j]:表示考虑前i个物品的情况下,重量最大为j的情况下,最大价值是多少
反推:
假设最优解一定为f[n][m]
判断第n个物品是否选择,遍历f[n-1][m],实际就是看f[n-1][m]是从哪个状态转移的

1.如果 f[n][m]=f[n-1][m] ,不选第n个物品

2.反之,如果f[n][m]=f[n-1][m-v[i]]+w[i],选这个物品,得到最优解。

枚举:从后往前枚举,
如果可以选第一个物品,一定要选它,保证字典序最小

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=1010;
int n,m; 
int f[N][N],v[N],w[N]; 
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	cin>>v[i]>>w[i];
	for(int i=n;i>=1;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]);
	  }
	  int vol=m; //反推,最开始体积为m 
	  for(int i=1;i<=n;i++//从前往后看物品,若能选 
	     if(f[i][vol]==f[i+1][vol-v[i]]+w[i]) //当前体积下f[i]和f[i+1]相同 ,都选择 
	     {
	     	cout<<i<<' ';
	     	vol-=v[i];
		 }
		 return 0;
	}

9.有依赖的背包问题

问题:
有N件物品和一个容量是V的背包。

物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
如图所示
在这里插入图片描述

如果选择物品5,则必须选择物品1和2,这是因为2是5的父节点,1是2的父节点。
每件物品的编号是i,体积是vi,价值是wi,依赖的父节点编号是pi。物品的下标范围是1…N.

求解将哪些物品装入背包,可使这些物品的总体积不超过背包的容量,且价值总和最大。
输出最大价值

输入格式
第一行有两个整数,N,V,用空格隔开,分别表示物品个数、背包容量。

接下来有N行,每行数据表示一个物品
第i行有三个整数vi,wi,pi,用空格隔开,分别表示物品的体积、价值和依赖的物品编号。
如果pi=-1,表示根节点,数据保证所有物品构成一棵树。

输出格式
输出一个整数,表示最大价值

数据范围
1<N,V<=100
0<vi,wi<=100
父节点编号范围:
内部节点:1<=pi<=N;
根节点:pi=-1;

输入样例
5 7
2 3 -1
2 2 1
3 5 1
4 7 2
3 6 2

输出样例
11

分析思路:

背包和树形Dp结合(转化为分组背包问题)

每个结点,把它们对应的子节点都递归计算一下,算出每个子节点不同体积下的最大价值;,每个子节点都是一个物品组,不同体积对应到不同组;整个组里面只能选择一个物品

f[i][j]表示选结点i的情况下,所用的体积是j的情况下,以i为根的整棵子树的最大价值是多少

从上往下递归求解,每做完一个结点,先把它的所有子节点的f[i][j]都算出,每个子节点对应在不同体积下,它们要对应的价值

for(int j=m-v[u];j>=0;j--) //枚举体积 ,(m减当前物品的体积)留一个空位,从大到小(只能选一次) 

若体积大于等于当前物品体积,需要在之前空出的位置,把这个物品加进去
f[u][i-v[u]]+w[u];更新的价值

若体积小于等于当前物品体积,整个子树一个节点都不选择(依赖性)



  for(int k=0;k<=j;k++)  //枚举物品组里面的每个物品 
		      f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]); //更新,看做一维 
	  //每个节点都会有一个f[j],把f[u][j]看做01背包的f 
	  

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

const int N=110;
int n,m; 
int h[N],e[N],ne[N],idx;  
int v[N],w[N],f[N][N];

void add(int a,int b){
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{  //先把所有的子节点都算出来 
	for(int i=h[u];i!=-1;i=ne[i]){ //先枚举物品组 
		int son=e[i];
		dfs(son); //每个子节点都是一个物品组 
		//这里的物品必须要选择 ,依赖性 
		for(int j=m-v[u];j>=0;j--) //枚举体积 ,留一个空位,从大到小(只能选一次) 
		   for(int k=0;k<=j;k++)  //枚举物品组里面的每个物品 
		      f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]); //更新,看做一维 
	  //每个节点都会有一个f[j],把f[u][j]看做01背包的f 
	}
	for(int i=m;i>=v[u];i--)
	 f[u][i-v[u]]+w[u];
	 for(int i=0;i<v[u];i++)
	  f[u][i]=0;
}
int main(){
	memset(h,-1,sizeof h);
	cin>>n>>m;
	int root;
	for(int i=1;i<=n;i++)
	{
		int p;
		cin>>v[i]>>w[i]>>p;
		if(p==-1)
		root=i;
		else add(p,i); 
	}
	dfs(root);
	cout<<f[root][m]<<endl; //初始化的时候把所有体积都初始化为0,表示体积最多是m的情况下,最大价值为多少 
	return 0;
}

以上内容若有不足之处,欢迎评论指教。
声明:本人博客在未经允许的情况下,严谨他人转载或抄袭。

  • 15
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值