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]-kw,余数是j模v的第k个数
第一个数减0w,f[0]=0
f[v]变为:f[v]-1w
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;
}
以上内容若有不足之处,欢迎评论指教。
声明:本人博客在未经允许的情况下,严谨他人转载或抄袭。