背包

北慕城南 提交于 2020-03-02 12:16:31

01背包

问题描述:容量为V的背包,n件物品,每件物品的价值为v[i],占空间为w[i],尽量让背包装的价值大。
解法:
寻找递推关系式:
面对当前的物品有两种选择:
(1)不选择,那么此时的价值与前面的价值相同,v[i][j]=v[i-1][j]
(2)选上,那么此时的价值就是前面的总物品的价值加上当前物品的价值,容量减去当前物品的容量 v[i][j]=v[i-1][j-w[i]]+v[i]
如果现在总的容量不够,那么只能不选。
所以推出关系式v[i][j]=max(v[i-1][j],v[i-1][j-w[i]]+v[i]).

Bone Collector

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e3+10;
int n,V;
int w[maxn],v[maxn];
int dp[maxn][maxn];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		memset(w,0,sizeof(w));
		memset(v,0,sizeof(v));
		memset(dp,0,sizeof(dp));
		scanf("%d%d",&n,&V);
		for(int i=1;i<=n;i++)
		scanf("%d",&v[i]);
		for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
		for(int i=1;i<=n;i++){
			for(int j=0;j<=V;j++){
				if(j>=w[i])
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
				else dp[i][j]=dp[i-1][j];
			}
		}
		printf("%d\n",dp[n][V]);
	}
	return 0;
}

滚动优化

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e3+10;
int n,V;
int w[maxn],v[maxn];
int dp[maxn];
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		memset(w,0,sizeof(w));
		memset(v,0,sizeof(v));
		memset(dp,0,sizeof(dp));
		scanf("%d%d",&n,&V);
		for(int i=1;i<=n;i++)
		scanf("%d",&v[i]);
		for(int i=1;i<=n;i++)
		scanf("%d",&w[i]);
		for(int i=1;i<=n;i++){
			for(int j=V;j>=w[i];j--){
				dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
			}
		}
		printf("%d\n",dp[V]);
	}
	return 0;
}

P2925 [USACO08DEC]干草出售Hay For Sale

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5;
int c,t;
int w[maxn];
int dp[maxn];
int main(){
	scanf("%d%d",&c,&t);
	for(int i=1;i<=t;i++)
	scanf("%d",&w[i]);
	for(int i=1;i<=t;i++){
		for(int j=c;j>=w[i];j--){
			dp[j]=max(dp[j],dp[j-w[i]]+w[i]);
		}
	}
	printf("%d\n",dp[c]);
	return 0;
}

完全背包

参考博客
与01背包最大的区别是,每种物品可以选无数次。
完全背包其实也可以理解为很多物品的01背包,因为总容量毕竟是有限的,所以只选一种物品不会真正无穷。
所以递推关系式为:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-kw[i]]+kv[i]) k(0~j/w[i])
按照暴力来表示:
三重循环,肯定是不可以的

for(int i=1;i<=n;i++){
for(int j=0;j<=V;j++){
for(int k=0;k<=j/w[i];k++){
}
}
}

我们假设dp[i][j]表示出在前i种物品中选取若干件物品放入容量为j的背包所得的最大价值。
那么对于第i种,如果不选的话那么dp[i][j]=dp[i-1][j],
如果选的话,那么至少选择一件,dp[i][j]=dp[i][j-w[i]],为什么是dp[i][j-w[i]]呢,因为不知道第i件是否已经选择。
优化三重到两重
公式推导
dp[i][j]=max(dp[i-1][j],dp[i-1][j-kw[i]]+kv[i]);
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i],dp[i-1][j-2w[i]]+2v[i],…,)
dp[i][j-w[i]]=max(dp[i-1][j-w[i]],dp[i-1][j-2w[i]],dp[i-1][j-3w[i]]…)
所以dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]])

for(int i=1;i<=n;i++){
for(int j=0;j<=V;j++){
dp[i][j]=dp[i-1][j];
if(j>=w[i]) 
dp[i][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
}
}

再进一步优化

for(int i=1;i<=n;i++){
for(int j=v[i];j<=V;j++){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}

01背包与多重背包的区别

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int dp[maxn],v[maxn],w[maxn];
int n,m;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&v[i],&w[i]);
	}
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<=m;j++){
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
			cout<<dp[j]<<" ";
		}
		cout<<endl;
	}
	cout<<endl;
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--){
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
			cout<<dp[j]<<" ";
		}
		cout<<endl;
	}
	return 0;
}

在这里插入图片描述

P1616 疯狂的采药

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
ll t;
const int maxn=1e4+10;
const int maxn1=1e5+10;
ll w[maxn],v[maxn];
ll dp[maxn1];//注意是跟t
int main(){
	scanf("%lld%d",&t,&n);
	for(int i=1;i<=n;i++)
	scanf("%lld%lld",&w[i],&v[i]);
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<=t;j++){
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		}
	}
	printf("%lld\n",dp[t]);//注意
	return 0;
}

多重背包

多重背包是规定了每一个物品的数量

f[i][j]=max(f[i-1][j],f[i-1][j-k*w[i]]+k*v[i])  //(0<=k<=p[i])

方法:我们可以将第i种换成若干件物品转化为01背包。

for(int i=1;i<=n;i++){
	for(int k=1;k<=m[i];k++){
		for(int j=V;j>=w[i];j--){
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
}

如果是上面这样的话对于大一点的数据显然是不行的。
比如说7 把它分成1 1 1 1 1 1 1 这7种物品
这7种物品又分选和不选 也就是0~7
那么接下来就是二进制拆分 分成1 2 4
如果是0 ,那么1 2 4都不选择
如果是1,那么选择1
如果是2,选择2
如果是3,选择3
如果是5,选择1和4
…也就是说0~7可以用这三个数表示
再比如10,那就是1,2,4,3
其中每件物品都有一个系数p[i],使这些系数为1,2,4,…,2^(k-1),p[i]-2 ^k+1,且k是满足p[i]-k+1>0的最大整数,例如p[i]=13,则分为1,2,4,6。
这样我们将O(N∑pi)转化为O(N∑log(pi))
二进制代码如下:

for(int i=1;i<=n;i++){
	int num=min(p[i],V/w[i]);
	for(int k=1;num>0;k<<=1){
		if(k>num)k=num;
		num-=k;
		for(int j=V;j>=w[i]*k;j--)
		f[j]=max(f[j],f[j-w[i]*k]+k*v[i]);
	}
}

P1776 宝物筛选

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e2+10;
const int maxn1=4e4+10;
int n,V;
int v[maxn],w[maxn],m[maxn];
int dp[maxn1];
int main(){
	scanf("%d%d",&n,&V);
	for(int i=1;i<=n;i++)
	scanf("%d%d%d",&v[i],&w[i],&m[i]);
	for(int i=1;i<=n;i++){
		int num=min(m[i],V/w[i]);
		for(int k=1;num>0;k<<=1){
			if(num<k) k=num;
			num-=k;
			for(int j=V;j>=k*w[i];j--){
				dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
			}
		}
	}
	printf("%d\n",dp[V]);
	return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!