几个背包问题的简述:
01背包:
完全背包:
多重背包:
01背包
朴素解法:
#include <iostream>
#include <algo>
#define maxn 1010
using namespace std;
int n,m;
int w[maxn],v[maxn];
int f[maxn][maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){ //第i个物体的时候
for(int j=0;j<=m;j++){ //容量有一点不剩的情况 第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]); //前提是装得下 才会有右边的情况
}
}
cout<<f[n][m]<<endl;
return 0;
}
优化解法:
#include <iostream>
#include <algorithm>
#define maxn 1010
using namespace std;
int n,m;
int v[maxn],w[maxn];
int f[maxn];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
//二维改成一维 就默认 每一次的f[j]是当前i的 而不是朴素解法之中的赋成i-1的 这样就会出问题: f[i][j-k] 是这一回已经更新过的 所以会一个用两次得情况
for(int j=m;j>=v[i];j--){
//装不下得情况就不用考虑了 那说明就一定没有i 这层循环就跳过去了
f[j]=f[j];
//可以 相当于f[i][j]=f[i-1][j]; 因为即便f[i][j-k]已经更新到i个的状态 但是f[x][j]没更新到 仍然是f[i-1][j] 所依无论体积正倒遍历 这个都可以
if(j>=v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
//这个f[j-v[i]]正不可以 因为正的话重量更轻的在这轮i已经都更新过了 这时状态已经包含了i 而后面又多加了一个i 与题目矛盾 想那个右半边子集的两部分也可以
}
}
cout<<f[m]<<endl;
return 0;
}
完全背包:
朴素解法:
#include <iostream>
#include <algorithm>
#define maxn 1010
using namespace std;
int n,m;
int v[maxn],w[maxn];
int f[maxn][maxn];
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=1;j<=m;j++){ //从1开始是为什么???
f[i][j]=f[i-1][j];
if(j>=v[i]) f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
//f[i][j-v[i]]的最大值+w[i]就是f[i][j]第一项往后之后的最大值 所以必须从前往后遍历体积
}
}
cout<<f[n][m]<<endl;
return 0;
}
优化算法:
#include <iostream>
#include <algorithm>
#define maxn 1010
using namespace std;
int n,m;
int v[maxn],w[maxn],f[maxn];
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=v[i];j<=m;j++){
//装不下的话一定是上一个能装的下的物品状态 所以一维数组没有被遍历的j存的并不会改变还是上一个能装得下的物品的状态
f[j]=max(f[j],f[j-v[i]]+w[i]);
//装得下的情况下 左边是上一个物品状态 右边是所有要装i的总共的最大 这些和f[i][j-v[i]]的最大值加一个w[i]相等 而这个f[j-v[i]]在i层算是在f[j]以前确定过了
}
}
cout<<f[m]<<endl;
return 0;
}
多重背包:
关于二进制表示法:
二进制表示所有连续整数的方法是不同2次幂权值选择0或1,所以如果按照二进制把众多物品打包成若干小背包,每个背包里面有2^n个物品,则用01的原理,也就是选择(1)或者不选择(0)这个小背包就可以把选择物品数的连续整数全都表示出来,而且时间复杂度是o(nlogn)
朴素解法:
#include <iostream>
#include <algorithm>
#define maxn 110
using namespace std;
int n,m;
int v[maxn],w[maxn],s[maxn];
int f[maxn][maxn];
int main(){
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=0;j<=m;j++){
for(int k=0;k<=s[i] && k*v[i]<=j;k++){ //从不放到放k个
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
cout<<f[n][m];
return 0;
}
优化解法:
#include <iostream>
#include <algorithm>
#define maxn 25000
using namespace std;
int n,m;
int v[maxn],w[maxn];
int f[maxn];
int main(){
cin>>n>>m;
int cnt=0;
for(int i=1;i<=n;i++){ //要一个一个输入i参数 然后每个i打成一堆小包
int k=1; //用这个来作为二次幂 写在for里面每一次回到1
int a,b,s;
cin>>a>>b>>s;
while(k<=s){
cnt++;
v[cnt]=k*a; //小包里面有k个i 需要小包的体积和价值都乘以这么多
w[cnt]=k*b;
s-=k; //这个是为了确保小包表示后 所有小包中的i数量加起来不超过k 后面会有比较用上这个
k*=2;
}
if(s>0){ //k有可能和s之间有一段空隙 正好不是2的次幂的时候就是
cnt++;
v[cnt]=s*a;
w[cnt]=s*b;
}
}
n=cnt;
//拿那么多小包做01背包问题 这些小背包是第1个物体的众多小背包 第2个物体的 一直到第n个物体的所有小背包 累在一起的一个集合
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;
}
P1048 [NOIP2005 普及组] 采药
一个01背包的模板题:
#include <iostream>
#include <algorithm>
#define maxn 1010
using namespace std;
int t,n;
int v[maxn],w[maxn];
int f[maxn];
int main(){
cin>>t>>n;
for(int i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=n;i++){
for(int j=t;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[t]<<endl; //输出最后一个物品遍历完 时间正好也满了的状态最大
return 0;
}
P1616 疯狂的采药
完全背包模板题
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL N=1e4+5, M=1e7+5;
LL t,n;
LL v[N],w[N];
LL f[M];
int main(){
scanf("%lld%lld",&t,&n);
for(LL i=1;i<=n;i++){
scanf("%lld%lld",&v[i],&w[i]);
}
for(LL i=1;i<=n;i++){
for(LL j=v[i];j<=t;j++){ //如果从0开始 中间也有判断为啥会142
f[j]=max(f[j],f[j-v[i]]+w[i]); //从前往后 因为需要前面更新过的f[i-1][j-v[i]] f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]) //1 2 3 4 6.。。。。。一直到无穷列举补完 这样的话必有地推关系便是后面着无穷无尽议案长传
}
} // f[i][j-v[i] = f[i-1][j-v[i]]+f[i-1][j-2v[i]]+w[i]+...... max= f[i][j-v[i]]
//max + w[i]
printf("%lld",f[t]);
return 0;
}
P1049 [NOIP2001 普及组] 装箱问题
本质是01背包,没有说价值是多少但是让求剩余空间的最小值,也就是在包的体积承受范围内,找重量和的最大值,这时可以认为重量就是价值,从而在总重量制约下,找到剩余最少
#include <iostream>
#include <algorithm>
#define maxn 25000
using namespace std;
int n,m;
int v[maxn],w[maxn],f[maxn];
int main(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>v[i];
w[i]=v[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<<m-f[m]<<endl;
return 0;
}
P1833 樱花
多重背包问题,如果是无穷大的观赏次数就设成很大的数
#include <iostream>
#include <algorithm>
#define maxn 2500000
using namespace std;
int n;
int a,b,c,d;
int v[maxn],w[maxn],s,f[maxn];
int main(){
scanf("%d:%d %d:%d",&a,&b,&c,&d);
cin>>n;
int t=(c*60+d)-(a*60+b);
int cnt=0;
for(int i=1;i<=n;i++){
int k=1;
cin>>a>>b>>s;
if(s==0) s=9999999;
while(k<=s){
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s-=k;
k*=2;
}
if(s>0){
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
n=cnt;
for(int i=1;i<=n;i++){
for(int j=t;j>=v[i];j--){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[t]<<endl;
return 0;
}