1.01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。然后装包,求总价值最大。
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值
输入格式
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
首先每件物品仅有一件
不选择该物品: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i−1][j]
选 择 该 物品: f [ i ] [ j ] = f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=f[i−1][j−w[i]]+v[i]) f[i][j]=f[i−1][j−w[i]]+v[i])
所以状态转移方程 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i]) f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])
优化:
其中i用来表示状态,但是可以用j直接表示状态,但是如果从前遍历,那么前面的数据每次都会被从新覆盖,所以j需要从后遍历
就可以写成 f [ j ] = m a x ( f [ j ] , f [ j − v ] + w ) f[j]=max(f[j],f[j-v]+w) f[j]=max(f[j],f[j−v]+w);
完整代码
#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[1010];
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--)
f[j]=max(f[j],f[j-v]+w);
}
cout<<f[m]<<endl;
}
2.完全背包
01背包的延续,每件商品可以无限取。
那么每件商品就不是取不取两种状态,就得算作取0件,1件,2件。。。这么去下去
先从二维数组分析,那就是再加一个循环 循环 0 < = k ∗ w [ i ] < = j 0<=k∗w[i]<=j 0<=k∗w[i]<=j
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ) ∣ f[i][j]=max(f[i−1][j−k∗w[i]]+k∗v[i])∣ f[i][j]=max(f[i−1][j−k∗w[i]]+k∗v[i])∣
优化
其中i依然可以去掉,用j来表示,这时候记得01背包说过,前面的数据会被覆盖掉,但是,完全背包,因为每件商品无限取,所以不用考虑覆盖,只考虑怎么拿最多就可以,看了好几个教程都感觉有点懵逼,于是debug一下,豁然开朗,
我还贴心的加了理解提示 (╹▽╹)
好了 贴代码
#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[1010];
int main(){
cin>>n>>m;
for (int i=0;i<n;i++){
int v,w;
cin>>v>>w;
for(int j=v;j<=m;j++)
f[j]=max(f[j],f[j-v]+w);
}
cout<<f[m]<<endl;
}
3.多重背包1
多重背包限制了每件商品的个数,最多有多少个,相比完全背包,更应该把他比作01背包的延续
那么每件商品取法
取0件,1件,2件。但是最多取s件,
所以 可以比成这些多出来的商品也是独立的商品,在01背包基础上多一个循环。
直接贴代码了
#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[1010];
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
int v,w,s;
cin>>v>>w>>s;
for(int j=m;j>=v;j--)
for(int k=1;k<=s && k*v<=j;k++)
f[j]=max(f[j],f[j-k*v]+k*w);
}
cout<<f[m]<<endl;
}
4.多重背包2 (二进制优化)
多重背包在数据规模 v,w,s皆为100时候还可以
可是如果看到数据规模100020002000的时候就要考虑二进制优化了
首先思考一个问题,
给定一个任意的数,比如7,需要多少个数可以表示出0-7中任意一个数字
把七转换成二进制 就可以发现只需要 1 2 4这三个数即可表示7以内任意数
比如6用2+4。。。。
也就是需要log2x向上取整个。
如果遇到13,其实我们不需要1 2 4 8 这么大的数,直接用13-7=6 其中7表示目前的数可以标识出的最大值
那么就用1 2 4 6即可表示出所有的数
那么回头看这个问题,如果需要判断不超过某数量的商品,就不需要全部遍历了
比如1023件商品,那么就可以通过10次遍历即可求出
首先插入代码块,通过代码来理解这个过程(看注释)
#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[2010];
struct Good{int v,w;};
int main(){
vector<Good> goods;//定义一个模板,包含v,w;
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<<=1){
s-=k;//s把每次k(1,2,4,8,16)减去,表示还能拿多少件
goods.push_back({v*k,w*k});
//存入的就是分别取1,2,4,8,16件商品的价值和重量
}
if(s>0)goods.push_back({v*s,w*s});//把最后剩下的存放进去,就可以表示任意值
}
for(auto good:goods)
for(int j=m;j>=good.v;j--)
f[j]=max(f[j],f[j-good.v]+good.w);
cout<<f[m]<<endl;
}
同时Debug一下看下运行流程
3.多重背包 (分组优化)
看了半天没看懂,代码先敲了一遍。记录下来,有时间再研究。
#include <bits/stdc++.h>
using namespace std;
int n,m;
const int N=20010;
int f[N],g[N],q[N];
int main(){
cin>>n>>m;
for(int i=0;i<n;i++){
int v,w,s;
cin>>v>>w>>s;
memcpy(g,f,sizeof f);
for(int j=0;j<v;j++){
int hh=0,tt=-1;
for(int k=j;k<=m;k+=v){
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;
}
4.混合背包问题
看完了前面那个多重背包优化,再来看这个混合背包,真是有一种舒畅的感觉。
混合背包就是物品选择有01背包,完全背包,多重背包多种方式,并且数据规模也没有前一个那么大
那么就可以使用二进制优化
然后把前面的代码拼凑起来就OK了
#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[2010];
struct Good{int kind,v,w;};
int main(){
cin>>n>>m;
vector <Good>goods;
for(int i=0;i<n;i++){
int v,w,s;
cin>>v>>w>>s;
if(s<0)goods.push_back({-1,v,w});
else if(s==0)goods.push_back({0,v,w});
else{
for(int k=1;k<s;k<<=1){
s-=k;
goods.push_back({-1,v*k,w*k});
}
if(s>0)goods.push_back({-1,v*s,w*s});
}
}
for(auto good:goods){
if(good.kind<0)
for(int j=m;j>=good.v;j--)f[j]=max(f[j],f[j-good.v]+good.w);
else
for(int j=good.v;j<=m;j++)f[j]=max(f[j],f[j-good.v]+good.w);
}
cout<<f[m]<<endl;
}
二维费用的背包问题
二维费用问题,其实和01背包非常的像,只是多了一个体积的限制,那么,01背包当时我们使用的是一维空间,那么,这里使用二维空间即可,直接看代码
#include <bits/stdc++.h>
using namespace std;
int n,v,m;
int f[110][110];
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;
}
分组背包问题
分组背包问题就是给物品分组
每组只能使用一个商品
那么,每组商品就要分别取出最优的解
然后后面再去最优的解,会把前面不优的解覆盖
#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[110],v[110],w[110];
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++)
if(j>=v[k])
f[j]=max(f[j],f[j-v[k]]+w[k]);
}
cout<<f[m]<<endl;
}
背包问题求方案数
求方案数需要多一个数组来统计方案。
代码比较容易理解
#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[2010],g[2010];
const int mod=1e9+7;
int main(){
cin>>n>>m;
for(int i=0;i<=m;i++)g[i]=1;
for(int i=0;i<n;i++){
int v,w;
cin>>v>>w;
for(int j=m;j>=v;j--){
int value=f[j-v]+w;
if(value>f[j]){
f[j]=value;
g[j]=g[j-v];
}else if(f[j]==value)
g[j]=(g[j]+g[j-v])%mod;
}
}
cout<<g[m]<<endl;
}
未完待续。。。