背包问题入门看《图解算法》中9.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的情况下,总价值最大是多少
result = max{f[n][0~V]}
f[i][j]:
1.不选第i个物品,f[i][j] = f[i - 1][j]
2.选第i个物品,f[i][j] = f[i - 1][j - v[i]]
f[i][j] = max{1,2,}
初始化:f[0][0] = 0;
#include<stdio.h>
int max(int a,int b){
if(a>=b) return a;
else return b;
}
int main(){
int N=500;
int n,m; //n是物品数,m是背包体积
int f[N][N];
int v[N],w[N];
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d %d",&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], w[i] + f[i-1][j-v[i]]);
}
printf("%d",f[n][m]);
return 0;
}
我们发现每次再对新的一行进行计算时,只用到了上一行的信息,那么我们可以尝试把二维背包变为一维,如下:
int f[N];
int v[N],w[N];
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d %d",&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]);
1.不管背包是否装满的所有可能中价值最大的,此类问题的二维数组应初始化全部为0。
2.要求背包必须恰好装满, 这样需要把f [ 0 ] [ 0 ~ m] 初始化为0, 其余初始化为负无穷。保证结果一定是从f [0][x]传递过来。(0体积的背包)
二、完全背包问题
现在每一个物体都有无限个啦!可以重复选,其余部分与上一题完全相同。
直接一维背包开搞!
f[i] 表示总体积为i的情况下,最大价值是多少
result = f [m]
之前的代码是上面一行传递给下面一行,这次是同一行中前一列的最大价值和上面一行的同一列 传递给后一个
实现如下:
for(int i = 0;i < n; i++)
{
for(int j = v[i]; j <= m; j++) //从小到大
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
三、多重背包问题 1
多了一个条件,第 i 种物品最多有 si 件
输入样例
分别对应 vi,wi ,si
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例
10
f[i] 总体积是i的情况下,最大价值是多少
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 - 2 * v[i]] + 2 * w[i]...); //到s[i]个 做个循环
}
1. f[i] = 0; 所有初始化为0
result = f[m]
2. f[0] = 0; 其余初始化为复无穷 恰好装满
result = max{f[0...m]}
可以由01背包修改而来,只不过要多一个循环对于每一件物体拿不同数量的比较
#include<stdio.h>
int max(int a,int b){
if(a>=b) return a;
else return b;
}
int n,m;
int f[110];
int main(){
scanf("%d %d",&n,&m);
for(int i=0 ; i<n ;i++){
int v,w,s;
scanf("%d%d%d",&v,&w,&s);
for(int j=m ; j>=0 ; j--){
for(int k=1 ; k<=s && k*v <= j ; k++){
f[j] = max(f[j], f[j - k * v] + k * w);
}
}
}
printf("%d",f[m]);
}
四、多重背包问题 2
数据范围
0 < N ≤ 1000
0 < V ≤ 2000
0 < vi,wi,si ≤2000
与上一道问题完全一样,是多重背包的二进制优化方法 ,可以算数量级更大的输入
可以把每种物体拆开为 s!个,从而转换为01背包问题看, 但数量级还是很大,用二进制拆法
给定任意数x,最少选多少个数,可以拼成<=x 的所有的数呢 ?
答案是 log2(x) 的上取整
eg:
x = 7 选 1,2,4即可表示<=7的所有数字 7 = 1+2+4
x=10 选 1,2,4,3即可
这样我们就不需要把同一种物体拆称 si 个,而是拆成log2(si)上取整个物体,复杂度降低
#include<stdio.h>
int max(int a,int b){
if(a>=b) return a;
else return b;
}
struct GOOD{ //创建物体结构体,用来创建拆出来的新物体
int v,w;
};
int f[2010];
int n,m;
int main(){
struct GOOD goods[100000];
int t = 1; //放入goods结构体数组的计数器
scanf("%d%d",&n,&m);
for(int i=0 ; i<n ; i++){
int v,w,s;
scanf("%d%d%d",&v,&w,&s);
for(int k=1 ; k<=s ; k*=2){
s -= k;
goods[t].v = v*k;
goods[t].w = w*k;
t ++;
}
if(s>0) { //用来对应 10被分为 1,2,4,3的1情况
goods[t].v = v*s;
goods[t].w = w*s;
t++;
}
}
for(int i=0 ; i<t ; i++) {
for(int j=m ; j>=goods[i].v ; j--)
f[j] = max(f[j], f[j - goods[i].v] + goods[i].w);
}
printf("%d",f[m]);
}
五、多重背包问题 3 终极版 男人八题之一
数据范围
0 < N ≤ 1000
0 < V ≤ 20000
0 < vi,wi,si ≤ 20000
现在数量级超级大,用二进制优化方法也顶不住了
单调队列优化方法 #我现在还不会等我学完了回来补充
大佬题解
这是多重背包问题1的代码
for(int i=0 ; i<n ;i++){
int v,w,s;
scanf("%d%d%d",&v,&w,&s);
for(int j=m ; j>=0 ; j--){
for(int k=1 ; k<=s && k*v <= j ; k++){
f[j] = max(f[j], f[j - k * v] + k * w);
}
}
}
六、混合背包问题
有 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背包或者完全背包就直接存下来,如果是多重背包就拆成log2个存入
#include<stdio.h>
int max(int a,int b){
if(a>=b) return a;
else return b;
}
int n,m;
int f[1010];
struct GOOD{
int kind;
int v;
int w;
};
struct GOOD goods[10010];
int t=1;
int main(){
scanf("%d%d",&n,&m);
for(int i=0; i<n ;i++){ //录入物体数组
int v,w,s;
scanf("%d%d%d",&v,&w,&s);
if(s<0) {
goods[t].kind=-1;
goods[t].v=v;
goods[t].w=w;
t++;
}
else if(s==0){
goods[t].kind=0;
goods[t].v=v;
goods[t].w=w;
t++;
}
else{
for(int k=1; k<=s ; k*=2){
s -= k;
goods[t].kind = -1; //拆完以后就变成01背包了 ,所以是-1
goods[t].v = v*k;
goods[t].w = w*k;
t++;
}
if(s>0){
goods[t].kind = -1;
goods[t].v = v*s;
goods[t].w = w*s;
t++;
}
}
}
//遍历物体数组
for(int i=1 ; i<t ; i++){
if(goods[i].kind < 0){ //01背包从大到小
for(int j=m ; j>=goods[i].v ; j--) {
f[j] = max(f[j], f[j-goods[i].v] + goods[i].w);
}
}
else{ //完全背包从小到大
for(int j=goods[i].v ; j<=m ; j++){
f[j] = max(f[j], f[j-goods[i].v] + goods[i].w);
}
}
}
printf("%d",f[m]);
}
七、二维费用背包问题
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行三个整数,N , V , M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi, mi, wi,用空格隔开,分别表示第 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
f[i][j] 为体积是i,重量是j时背包的最大价值。
就是把质量看成和体积同等地位的一种限制条件,再来次循环 ,若再多一个限制条件就变成三维数组即可
#include<stdio.h>
int max(int a,int b){
if(a>=b) return a;
else return b;
}
int f[110][110];
int main(){
int n,v,m;
scanf("%d %d %d",&n,&v,&m);
for(int i=0;i<n;i++){
int a,b,c;
scanf("%d%d%d",&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);
}
printf("%d",f[v][m]);
}
八、分组背包问题
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 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的情况下,背包的最大价值
//很像01背包问题,只是其中每个物品都可以在此细分并且要选出其中最优的那个
for(int i = 0; i<n ; i++){ //循环每一组
for(int j=m;j>=v;j--){ //循环体积
for() //循环组中每一个物品 , 也可以一个不选
f[j] = max(...);
}
}
多重背包问题是分组背包问题的特殊情况,多重背包问题是每一个物体有s+1种选法(不选,和选1到s个),可以想象把这s+1种选法变成s个物体打包成一个物体组变为分组背包问题,分组背包问题也是s+1种,但没法优化。