背包

01 背包

\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(只能选一次)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。

\(dp[i][j]\) 选取前 \(i\) 件物品重量为 \(j\) 能取得的最大的权值,可以得到转移方程 \(dp[i][j]=max(dp[i-1][j-w[i]]+val[i],dp[i-1][j]);\)

因为 i 只与 i-1 有关所以考虑滚动数组:

交替滚动

用 now 表示现在的数组,用 old 表示上一个数组,交替使用。

#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[3][10010];

int main(){
	ios::sync_with_stdio(false);
	cin>>t>>m;
	for(int i=1;i<=m;i++){
		cin>>w[i]>>val[i];
	}
	int now=0,old=1;
	for(int i=1;i<=m;i++){
		swap(old,now);
		for(int j=0;j<=t;j++){
			if(j>=w[i]){
				dp[now][j]=max(dp[old][j],dp[old][j-w[i]]+val[i]);
			}
			else{
				dp[now][j]=dp[old][j];
			}
		}
	}
	cout<<dp[now][t];
    return 0;
}

自我滚动

因为体积只会向小减少,所以从后向前遍历容量以确保每个数物品只选一次,上一次的数组储存的数(i-1) 结果全部就在这个数组里,这样空间压缩到一维。

#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[10010];

int main(){
	ios::sync_with_stdio(false);
	cin>>t>>m;
	for(int i=1;i<=m;i++){
		cin>>w[i]>>val[i];
	}
	for(int i=1;i<=m;i++){
		for(int j=t;j>=w[i];j--){
			dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
		}
	}
	cout<<dp[t];
    return 0;
}

完全背包

\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(可以多次同选一个)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。

我们对 01 背包的代码进行更改为从小到大枚举容量,因为在计算某个容量时,可以确保之前计算的状态已经包含了该物品之前选择的次数,这样就能够正确地累积多个相同物品的价值。

#include<bits/stdc++.h>
using namespace std;
const int N=10010;
int t,m;
int w[202],val[202];
int dp[10010];

int main(){
	ios::sync_with_stdio(false);
	cin>>t>>m;
	for(int i=1;i<=m;i++){
		cin>>w[i]>>val[i];
	}
	for(int i=1;i<=m;i++){
		for(int j=w[i];j<=t;j++){
			dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
		}
	}
	cout<<dp[t];
    return 0;
}

多重背包

\(n\) 件物品,每件物品有权值和重量,给出背包体积 \(V\),从这些物品中挑选若干件(给出共有几个)放入背包,使得背包内物品的总重量不超过 \(V\),问能可以得到的最大权值。

考虑将每个物品进行二进制拆分,拆完后就转化为 01 背包问题。(因为是二进制,所以可以保证可以取到任意组合)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int t,m;

int w[N],val[N];
long long dp[N];
int main(){
	ios::sync_with_stdio(false);
	cin>>m>>t;
	int a,b,c;
	int cnt=0;
	for(int i=1;i<=m;i++){
		cin>>a>>b>>c;
		for(int j=1;j<=c;j<<=1){//二进制 
			w[++cnt]=j*a;//第cnt个背包的重量 
			val[cnt]=j*b;//计算权值 
			c-=j;//计算剩余 
		}
		if(c){//剩余单独储存
			w[++cnt]=c*a;
			val[cnt]=c*b;
		}
	}
	for(int i=1;i<=cnt;++i){//01背包选择 
		for(int j=t;j>=w[i];--j){
			dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
		}
	}
	cout<<dp[t];
    return 0;
}

分组背包

01背包规则,他的物品大致可分为 \(k\) 组,每组中的物品相互冲突,现在,问最大的价值是多少。

只是加入了组的限制,只需按组储存,然后 01 背包。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int w[10001];
int val[10001];
int b[100001];
int g[205][205];
int dp[100001];
int main(){
	ios::sync_with_stdio(false);
	cin>>m>>n;
	int t=0;
	for(int i=1;i<=n;i++){
		int x;//组 
		cin>>w[i]>>val[i]>>x;
		t=max(t,x);//最大组 
		b[x]++;//小组物品增加 
		g[x][b[x]]=i;//第 x 组第 b[x] 个物品的编号 
	}
	for(int i=1;i<=t;i++){//枚举组数 
		for(int j=m;j>=0;j--){//枚举容量 
			for(int k=1;k<=b[i];k++){//枚举第i组的物品 
				if(j>=w[g[i][k]]){
					dp[j]=max(dp[j],dp[j-w[g[i][k]]]+val[g[i][k]]);
				}
			}
		}
	}
	cout<<dp[m];
    return 0;
}

混合背包

背包中的物品有的可以选一次(0/1 背包),有的可以选多次(完全背包),有的数量有限(多重背包)。

无限个并不是真的无限,只需转化为最大容积除物品中重量的个数就行,然后问题就转化为多重背包了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int t,m;

int w[N],val[N];
long long dp[N];

int main(){
	ios::sync_with_stdio(false);
	cin>>t>>m;
	int a,b,c;
	int cnt=0;
	for(int i=1;i<=m;i++){
		cin>>a>>b>>c;//重,价,数	
		if(c==0){//无限个(最大容积除重量个) 
			c=t/a; 
		} 
		for(int j=1;j<=c;j<<=1){//剩下多重背包 
			w[++cnt]=j*a;
			val[cnt]=j*b;
			c-=j;
		}
		if(c){
			w[++cnt]=c*a;
			val[cnt]=c*b;
		}
	}
	for(int i=1;i<=cnt;++i){
		for(int j=t;j>=w[i];--j){//01背包 
			dp[j]=max(dp[j],dp[j-w[i]]+val[i]);
		}
	}
	cout<<dp[t];
    return 0;
}
posted @ 2024-09-05 21:35  sad_lin  阅读(6)  评论(0编辑  收藏  举报