背包问题~

例1 OJ267 01背包

【分析】

(一)二维

1.f[i][j]:前i个物品容量为j的最优解。

当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N 件物品,则需要 N 次决 策,每一次对第 i 件物品的决策,状态f[i][j]不断由之前的状态更新而来。

2.如果当前背包容量不够( j<v[i]),则当前的最优解由上一次的状态转移而来(f[i][j]=f[i-1][j])

3.对于容量足够的情况,我们有选择的余地,因而要对当前第i件物品进行选与不选的决策。

(1)选:f[i][j]=f[i-1][j-v[i]]+w[i](前 i−1i−1 件物品放入剩下的容量为 j−v[i]j−v[i] 的背包中的最大价值)

(2)不选:f[i][j]=f[i-1][j]

我们需要的是最大价值,因此每次对以上两种情况取max。

【代码1】

#include <bits/stdc++.h>
using namespace std;
int f[35][205];
int v[50],w[50];//v为空间 w为价值
int main(){
	int m,n;
	cin>>m>>n;
	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++){//背包空间的枚举
			f[i][j]=f[i-1][j];//承接上一个的最大价值
			if(v[i]<=j) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//当能够放得下时 比较原来的价值和放入当前物品后的价值 取较大值
		}
	}
	cout<<f[n][m];
	return 0;
}

 (二)一维

1.为什么可以优化成一维

首先观察状态转移方程, f[i][j]是由 f[i-1][jxxxx]推导而来,仅看第一个维度,即i - 1 与 i ,可以发现第i层是由上一层推导而来的。

故我们不需要保存i - 2 层,比如我们计算第三层是只需要第二层的,不需要第一层的数据。

当我们去掉i时,即我们不需要控制第几层,只需要长度为j的数组,保存确认过最新的一层。作为下一层的参考。

2.为什么第二层循环需要逆序

一维的f数组保存的是确认过的最新一层的数据,即上一层的数据。也就是说,我们要得到当前第i层的最优解,必须要从i-1层得到。如果正序,那么我们需要用到的i-1层是已经更新过的,不是原本上一层的i-1了。

相反,如果倒叙,从大到小就不存在这种问题,前面的第i-1层还是原来的i-1层。

3.状态转移方程:f[j]=max(f[j],f[j-v[i]+w[i]]

 【代码2】

#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int n[N],w[N];//v是重量 w是价值 
int f[N];//f[i][j]表示前i件物品在j个空间内能获得的最大价值 
int main(){
    int m,n;
    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>=v[i];j--){
           f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
cout<<f[m];
}

练习1 OJ1290 采药

#include <bits/stdc++.h>
using namespace std;
int  t[105],v[105];
int a[1005];
int main(){
	int T,M;
	cin>>T>>M;
	for(int i=1;i<=M;i++){
		cin>>t[i]>>v[i];
	}
	for(int i=1;i<=M;i++){
		for(int j=T;j>=0&&j>=t[i];j--){
			a[j]=max(a[j],a[j-t[i]]+v[i]);
		}
	}
	cout<<a[T];
	return 0;
}

例2 OJ268 完全背包

【分析】

【代码】

#include <bits/stdc++.h>
using namespace std;
int f[1005];
int v[1005],w[1005];
int main(){
	int m,n;
	cin>>m>>n;
	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<<"max="<<f[m];
	return 0;
}

 练习2 OJ293 买书

#include <bits/stdc++.h>
using namespace std;
int p[5]={0,10,20,50,100};//定义p数组存放每种书的价格 
int m,f[1005];//m为书的种类 
int main(){
	m=4;//一共有四种书可供购买 
	int n;
	cin>>n;
	f[0]=1;//初始化 
	for(int i=1;i<=m;i++){
		for(int j=p[i];j<=n;j++){//完全背包  
			f[j]+=f[j-p[i]];
		}
	}
	if(n==0) cout<<0;//n等于0时 特判 
	else cout<<f[n];
	return 0;
}


例3 ACwing 多重背包

【分析】

考虑k的个数 并且需要将多重背包转换成01背包

【代码1】

#include <bits/stdc++.h>
using namespace std;
const int N=1005;
int v[N],w[N],s[N];//v是重量 w是价值 s是物品数量
int f[N];//f[i][j]表示前i件物品在j个空间内能获得的最大价值 
int main(){
	int m,n;
	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>=v[i];j--){//空间从大到小
			for(int k=1;k<=j/v[i] &&k<=s[i];k++)//k为可以使用的次数且不超过最大数量 
				f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);//多乘个k就🆗
		}
	}
	cout<<f[m];		
}

【代码2】二进制优化版

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;//2e5=2*10^5 大一点 
int f[N];
int v[N],w[N],s[N];//v体积 w价值 s数量 
int main(){
	int n,m;
	cin>>n>>m;
	int l=0;//l用来表示堆数
	int a,b,c;//a表示单个物品的体积 b表示单个物品的价值 c表示该物品的个数 
	for(int i=1;i<=n;i++){
		cin>>a>>b>>c;
		int k=1;//初始化放入的个数k 为一个 
		while(c>=k){//当剩余的数量比k大时 
			l++;//开下一个堆 
			v[l]=a*k; 
			w[l]=b*k;//l个物品的空间和价值分别为单个空间和价值乘以k个 
			c-=k;//每放入k个需要在c中减去 
			k*=2;//k二进制递加 
		}
		if(c>0){//如果还有剩余的物品 
			v[++l]=a*c;
			w[l]=b*c;//将剩余的空间和价值全部加进数组中 
		}
	}
	for(int i=1;i<=l;i++){
		for(int j=m;j>=v[i];j--){
			f[j]=max(f[j],f[j-v[i]]+w[i]);//01背包处理 
		}
	}
	cout<<f[m];
	return 0;
}

例4 AcWing 混合背包

【代码】

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int v[N],w[N];
int f[N];
int main(){
	int n,m;
	cin>>n>>m;
	int a,b,c,l=0;
	for(int i=1;i<=n;i++){
		cin>>a>>b>>c;
		if(c==0){
			for(int j=a;j<=m;j++){
				f[j]=max(f[j],f[j-a]+b);
			}//完全背包
		}
		else{
			if(c==-1) c=1;
			int k=1;
			while(c>=k){
				v[++l]=a*k;
				w[l]=b*k;
				c-=k;
				k*=2;
			}
			if(c){
				v[++l]=c*a;
				w[l]=c*b;
			}
		}
	}//将多重背包转换成01背包 详见例3
	for(int i=1;i<=l;i++){//从1到l
		for(int j=m;j>=v[i];j--){
			f[j]=max(f[j],f[j-v[i]]+w[i]);
		}
	}
	cout<<f[m];
	return 0;
} 

例5 AcWing 二维费用

【代码】

#include <bits/stdc++.h>
using namespace std;
int a[1005],b[1005],c[1005];
int f[1005][1005];
int main(){
	int s,n,m;
	cin>>s>>n>>m;
	for(int i=1;i<=s;i++){
		cin>>a[i]>>b[i]>>c[i];
	}
	for(int i=1;i<=s;i++){
		for(int j=n;j>=a[i];j--){
			for(int k=m;k>=b[i];k--){
				f[j][k]=max(f[j][k],f[j-a[i]][k-b[i]]+c[i]);
			}
		}
	}
	cout<<f[n][m];
	return 0; 
}

例6 OJ1272 分组背包

【代码】

#include <bits/stdc++.h>
using namespace std;
int v[1005][1005],w[1005][1005];
int f[10005][10005];
int a,b,c;
int V,N,T;
int main(){
	cin>>V>>N>>T;
	for(int i=1;i<=N;i++){
		cin>>a>>b>>c;
		v[c][0]++;//第c组有多少个物品
		int l=v[c][0];
		v[c][l]=a;
		w[c][l]=b;//将每个物品的重量和价值存入数组中
	}
	for(int i=1;i<=T;i++){//组别枚举
		for(int j=0;j<=V;j++){//容量枚举
			f[i][j]=f[i-1][j];//不选的情况下 承接上一个
			for(int k=0;k<=v[i][0];k++){//当前组别中每种物品的枚举
				if(j>=v[i][k]) f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);//在容量j大于当前物品的重量的情况下(否则会出现复数)
			}
		}
	}
	cout<<f[T][V];
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值