01背包
01背包的状态转移方程f[i,j] = Max{f[i-1,j-wi]+pi(j>=wi),f[i-1,j]}
f[i,j]表示在前i件物品中选择若干件放在承重为j的背包中,可以取得的最大价值。
pi表示第i件物品的价值。
决策:为了背包中物品总价值最大化,第 i件物品应该放入背包中吗 ?
题意描述:
假设山洞里共有a,b,c,d ,e这5件宝物(不是5种宝物),它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富。
有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
| n | w | v | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| a | 2 | 6 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 |15 | 15 |
| b | 2 | 3 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
| c | 6 | 5 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
| d | 5 | 4 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10|
| e | 4 | 6 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
只要你能通过找规律手工填写出上面这张表就算理解了01背包的动态规划算法。
首先要明确这张表是至底向上,从左到右生成的。
当宝物为e时,宝物重量为4而当背包容量为1时,承载不了宝物,故value为0;
随着背包容量增大,能够承载宝物e,则value为6;
然后再增加一个宝物d,当宝物为e时,value最大时,此时的背包重量减去宝物d的重量,加上宝物d的value。以此类推。
动态规划的实质就是将大问题分解成小问题逐个击破,以实现最终问题的解决
实现代码参考
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
int Max(int a,int b)
{
int max;
if(a>b)
max=a;
else
max=b;
return max;
}
int f[10100][10100],w[10100],p[10100];
int main()
{
memset(f,0,sizeof(f));
int n,value=0;
scanf("%d",&n);//有多少个物体
for(int i=0;i<n;i++)//物体重量
{
scanf("%d",&w[i]);
}
for(int i=0;i<n;i++)//物体价值
{
scanf("%d",&p[i]);
}
for(int i=4;i>=0;i--)//从底层开始
{
for(int j=1;j<=10;j++)
{
if(j-w[i]>=0)//判断背包是否装得下
{
f[i-1][j]=Max(f[i][j],f[i][j-w[i]]+p[i]);
value=f[i-1][j];
}
else
value=f[i][j];
}
}
printf("%d",value);
}
2021/6/2
以下是经过了大一下半学期的学习首次将九个背包完整的了解完,有一些简单的想法,记录一下学习经历
下面是对01背包的一维数组优化
背包问题的一维数组优化都可以看成在不改变二维数组含义的条件下直接删去一个数组
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int f[N],w[N],v[N];
int main()
{
int n,m;
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]);
cout<<f[m]<<endl;
return 0;
}
完全背包
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
如图一
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int main()
{
int n,m;
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]<<endl;
}
完全背包的优化和01背包超级超级像,这里可以直接简单的记着,具体详情请去https://www.acwing.com/video/215/ 了解
//01背包
for(int i=1; i<=n; i++)
for(int j=n; j>=v[i]; j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
//完全背包
for(int i=1; i<=n; i++)
for(int j=v[i]; j<=n; j++)
f[j]=max(f[j],f[j-v[i]]+w[i]);
下面是完全背包优化后的代码
#include<bits/stdc++.h>
using namespace std;
int v[1101],w[1010];
int f[1010];
int main()
{
int n,m;
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=v[i]; j<=m; j++){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m];
}
多重背包
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
多重背包一
多重背包和完全背包相似,具体来说就是将完全背包里面的每种物品无限个变为了给定你每种物品有多少个
一种方法就是直接硬算,这个只能适用于数据较小的情况下
#include<iostream>
#include<algorithm>
using namespace std;
int s[101],v[101],w[101];
int f[1010];
int main()
{
int n,m;
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=m; j>=0; j--){
for(int k=1; k<=s[i]&&k*v[i]<=j; k++){
//用循环枚举每种物品所有能选的个数的情况
f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
}
}
}
cout<<f[m];
}
还有一种方法也是挺好理解的,就是把每种物品有多个数量的情况下把它拆分成不同的种类,也就是说增加了物品种类数,把多重背包拆分成了多个01背包,之后直接套用01背包的模板就行了
while(n--)//本来有的种类数
{
cin>>v>>w>>s;//每种背包的体积,价值和数量
while(s--)//美剧数量
{a[++t]=v;
b[t]=w;}//死拆
}
多重背包二
这里实际上就是多包一的plus版,所有的数据都会变得超级大,暴力枚举肯定不行,暴力拆分成01背包也是会炸。。
这时候就有另一种优化方法,二进制优化法。
顾名思义二进制优化就是将物品数量用二进制分开,拆分成若干份小的种类,如果不是特别清楚可以找个例子试试。
如果你要拆分11个物品,正常情况下你要拆分成(1,1,1,1,1.。。。)拆分成11个1共计十一次。
但是用1,2,2,4,拆成这四份,可以组成任意从1到11,任意一个数。这就是二进制拆分
11用二进制表示就是1011(B),所以11就可以拆分成1011(B)=0111(B)+0100(B), 此时,0111(B)就是11能拆分出含有1数量最多的方案数,0111(B)又可以拆分成0100(B)0010(B)0001(B)分别对应4,2,1再算上刚才拆分出的0100(B),由此可以得出11可以拆分成1,2,2,4。
这种优化对于大数尤其明显,例如有1024个商品,在正常情况下要枚举1025次 , 二进制思想下转化成01背包只需要枚举10次。
个人认为这是一个玄学的东西
下面就是二进制优化的具体处理
for(int j=1;j<s[i];j<<=1)//二进制拆分
{
a[total]=j*w[i];//存价值
b[total++]=j*v[i];//存容量
s[i]-=j;
}
if(s[i]>0)
{
a[total]=s[i]*w[i];
b[total++]=s[i]*v[i];
}
也可以用vector容器处理
for(int k = 1 ; k <= s ;k*=2)
{
s-=k;
goods.push_back({v*k,w*k});
}
if(s>0)
goods.push_back({v*s,w*s});
多重背包三
暂时不会,以后补充
混合背包问题
有 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 次;
混合背包就是将上边三类背包综合一下,用si进行限制
可以将多重背包化成01背包处理,然后将完全背包单独处理
完整AC代码
#include<bits/stdc++.h>
using namespace std;
int f[101010],v[10101],w[10101],s[10101];
int main()
{
int n,m,a,b,c,l=0;
cin>>n>>m;
for(int i=1; i<=n; i++)
{
cin>>a>>b>>c;
if(c>0)
{
for(int k=1; k<=c; k=k*2)
{
c=c-k;//混合背包拆分成01背包
v[l]=k*a;
w[l]=k*b;
s[l++]=1;
}
if(c>0)
{
v[l]=c*a;
w[l]=c*b;
s[l++]=1;
}
}
else
{
if(c==-1) s[l]=1;
else s[l]=-1;
v[l]=a;
w[l++]=b;
}
}
for(int i=0; i<l; i++)
{
if(s[i]==1)
{
for(int j=m; j>=v[i]; j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
else
for(int j=v[i]; j<=m; j++)
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
}
二维费用的背包问题
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。
二维费用顾名思义,就是这个背包本来是被容量限制,现在又多了一层限制,又多了一个重量限制,这样就需要用二维分别限制。
可以将其视为两重01背包
完整代码如下
#include<bits/stdc++.h>
using namespace std;
int f[1001][1010];
int v[1010],m[1010],w[1010];
int main()
{
int N,V,M;
cin>>N>>V>>M;
for(int i=1; i<=N; i++) cin>>v[i]>>m[i]>>w[i];
for(int i=1; i<=N; i++)
for(int j=V; j>=v[i]; j--)
for(int k=M; k>=m[i]; k--)
f[j][k]=max(f[j][k],f[j-v[i]][k-m[i]]+w[i]);
cout<<f[V][M]<<endl;
}
个人认为这个和Floyd最短路径算法神神神似
for(int k=1; k<=n; k++)
for(int i=1; i<=n; i++)
for(int j=1; j<=n; j++)
{
if(f[i][j]>f[i][k]+f[k][j])
f[i][j]=f[i][k]+f[k][j];
}
分组背包问题
有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
有依赖的背包问题
暂时不会,以后补充。。。