基础背包问题(01,完全,多重)

背包问题

01 01 01背包

题目描述:有 n n n件物品和一个容量为 v v v的背包,每个物品放进背包需要占用 S i S_i Si的空间,可以得到 W i W_i Wi的价值,放入一些物品使得价值最大,求最大值

分析:

状态设计:
  1. 明显的发现这个问题与 v , S i , W i v,S_i,W_i v,Si,Wi有关,于是可以将空间开出一维,价值用 F F F数组的值表示,设 F j F_{j} Fj表示恰好占用 j j j空间的最大价值
  2. 因为 D P DP DP要从小问题得到大问题的解,由于物品排列的先后顺序不会影响答案,就可以加多一维 i i i,表示 F i , j F_{i,j} Fi,j i i i个物品中,占用空间恰好为 j j j的最大价值
转移方程:
  1. 假设当前我们要求 F i , j F_{i,j} Fi,j,那么对于第 i i i个物品,就有取和不取,于是就有 F i , j = { F i − 1 , j ( 不 取 ) ( 0 ⩽ j ⩽ v ) F i − 1 , j − S i + W i ( 取 ) ( s i ⩽ j ⩽ v ) F_{i,j}=\left\{ \begin{aligned} F_{i-1,j} (不取)(0\leqslant j \leqslant v)\\ F_{i-1,j-S_i}+W_i (取)(s_i\leqslant j \leqslant v)\\ \end{aligned} \right. Fi,j={Fi1,j(0jv)Fi1,jSi+Wi(sijv)
  2. 于是枚举 i , j i,j i,j即可解决问题
时空优化:
  1. 时间上复杂度为 O ( n v ) O(nv) O(nv),无法优化
  2. 空间上复杂度为 O ( n v ) O(nv) O(nv),发现 F i , j F_{i,j} Fi,j只与 F i − 1 , j ( 0 ⩽ j ⩽ v ) F_{i-1,j(0\leqslant j \leqslant v)} Fi1,j(0jv)有关,于是可以把 i i i用滚动数组滚掉。
F F F初始化:
  1. 明显的全部为 0 0 0即可

代码实现(优化):

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

void read(int &sum)
{
	sum=0;char last='w',ch=getchar();
	while (ch<'0' || ch>'9') last=ch,ch=getchar();
	while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
	if (last=='-') sum=-sum;
}
int old[1010],now[1010];
struct node { int s,w; }a[1010];
int n,v;
int main()
{
//	freopen("M.in","r",stdin);
//	freopen("M.out","w",stdout);
	read(n),read(v);
	for (int i=1;i<=n;i++) read(a[i].s),read(a[i].w);
	memset(old,0,sizeof(old));
	memset(now,0,sizeof(now));
	for (int i=1;i<=n;i++)
	{
		for (int j=0;j<=v;j++)
		{
			now[j]=old[j];
			if (j>=a[i].s)
			{
				now[j]=max(now[j],old[j-a[i].s]+a[i].w);
			}
		}
		for (int j=0;j<=v;j++)
			old[j]=now[j];
	}
	int ans=0;
	for (int i=0;i<=v;i++) ans=max(ans,old[i]);
	printf("%d",ans);
//	fclose(stdin);fclose(stdout);
	return 0;
}

例题:采药

完全背包

题目描述:题目描述:有 n n n种物品和一个容量为 v v v的背包,每种物品放一个进背包需要占用 S i S_i Si的空间,可以得到 W i W_i Wi的价值,放入一些物品使得价值最大,每种物品可以有无数个(空间大小要满足条件),求最大值

分析:

状态设计:
  1. 01 01 01背包同理,设 F i , j F_{i,j} Fi,j表示前 i i i种物品恰好占用 j j j空间物品的最大价值
转移方程:
  1. 假设当前选择到第 i i i种物品,要选择 k k k个,那么 F i , j F_{i,j} Fi,j可能由 F i − 1 , j − k ∗ S i F_{i-1,j-k*S_i} Fi1,jkSi,或者直接继承 F i − 1 , j F_{i-1,j} Fi1,j
时空优化:
  1. 原时间复杂度为 O ( n v ∑ i = 1 n ( v S i ) ) O(nv\sum_{i=1}^n(\frac{v}{S_i})) O(nvi=1n(Siv)),空间为 O ( n v ) O(nv) O(nv)
  2. 现在考虑优化,假设有两种状态分别为 F i , j − S i , F i , j F_{i,j-S_i},F_{i,j} Fi,jSi,Fi,j,那我们在 D P DP DP,发现其实它们只有 F i , j − S i F_{i,j-S_i} Fi,jSi的差别,于是考虑减少次数
  3. 发现 F i , j − S i F_{i,j-S_i} Fi,jSi已经把除了 F i , j − S i F_{i,j-S_i} Fi,jSi之外的所有 F i , j F_{i,j} Fi,j要考虑的状态都考虑了,所以我们发现可以把 k k k去掉,从小到达枚举 j j j,每次只需转移 F i , j = F i , j − S i F_{i,j}=F_{i,j-S_i} Fi,j=Fi,jSi。注意要考虑不取的情况,即 F i , j = F i − 1 , j F_{i,j}=F_{i-1,j} Fi,j=Fi1,j
  4. 在加上一个滚动数组把 i i i滚掉,时间复杂度 O ( n v ) O(nv) O(nv),空间复杂度 O ( v ) O(v) O(v)

F F F初始化:

  1. 明显的全部为 0 0 0即可

代码实现(优化):

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
typedef long long ll;

void read(int &sum)
{
	sum=0;char last='w',ch=getchar();
	while (ch<'0' || ch>'9') last=ch,ch=getchar();
	while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
	if (last=='-') sum=-sum;
}
int n,v;
struct node { int s,w; }a[10010];
int f[10000010];
signed main()
{
//	freopen("M.in","r",stdin);
//	freopen("M.out","w",stdout);
	read(n),read(v);
	for (int i=1;i<=n;i++) read(a[i].s),read(a[i].w);
	for (int i=1;i<=n;i++)
	{
		for (int j=0;j<=v-a[i].s;j++)
			f[j+a[i].s]=max(f[j+a[i].s],f[j]+a[i].w);
	}
	int mmax=0;
	for (int i=0;i<=v;i++)
		mmax=max(f[i],mmax);
	printf("%lld",mmax);
	//	fclose(stdin);fclose(stdout);
	return 0;
}

例题:疯狂的采药

多重背包

题目描述:有 n n n种物品和一个容量为 v v v的背包,每种物品放一个进背包需要占用 S i S_i Si的空间,可以得到 W i W_i Wi的价值,放入一些物品使得价值最大,每种物品有 T i T_i Ti个(空间大小要满足条件),求最大值

分析:

状态设计:
  1. 同完全背包,设 F i , j F_{i,j} Fi,j表示前 i i i种物品恰好占用 j j j空间物品的最大价值
转移方程:
  1. 这个问题可以看作,是一个完全背包,只不过把有每种 v / S i v/S_i v/Si个,改成了 T i T_i Ti,可以按完全背包朴素的方法做, F i , j F_{i,j} Fi,j可能由 F i − 1 , j − k ∗ T i F_{i-1,j-k*T_i} Fi1,jkTi,或者直接继承 F i − 1 , j F_{i-1,j} Fi1,j
时空优化:
  1. 原时间复杂度为 O ( n v ∑ i = 1 n ( v S i ) ) O(nv\sum_{i=1}^n(\frac{v}{S_i})) O(nvi=1n(Siv)),空间为 O ( n v ) O(nv) O(nv)
  2. 考虑 k k k的问题,其实最多就 T i T_i Ti个,考虑到用几个 2 0 2^0 20~ 2 t 2^t 2t可以表示出 2 t + 1 − 1 2^{t+1}-1 2t+11里的所有数,所以将第 i i i种物品变成 log ⁡ 2 T i + 2 \log_2T_i+2 log2Ti+2个物品,设 log ⁡ 2 T i = g \log_2T_i=g log2Ti=g 1 1 1 ~ g − 1 g-1 g1是体积为 2 t 2^t 2t价值为 W i ∗ 2 t W_i*2^t Wi2t的物品, g g g个物品是体积为 T i − 2 g + 1 T_i-2^g+1 Ti2g+1价值为 W i ∗ ( T i − 2 g + 1 ) W_i*(T_i-2^g+1) Wi(Ti2g+1)的物品,于是这样就可以像 01 01 01背包一样做了
  3. 滚掉 i i i
  4. 时间复杂度 O ( n v ∑ i = 1 n log ⁡ 2 ( v S i ) ) O(nv\sum_{i=1}^n\log_2(\frac{v}{S_i})) O(nvi=1nlog2(Siv)),空间复杂度 O ( v ) O(v) O(v)
F F F初始化:
  1. 明显的全部为 0 0 0即可

代码实现(优化):

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

void read(int &sum)
{
	sum=0;char last='w',ch=getchar();
	while (ch<'0' || ch>'9') last=ch,ch=getchar();
	while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
	if (last=='-') sum=-sum;
}
int f[101000];
struct node { int s,w; }a[1010*63];
int n,v;
int main()
{
//	freopen("M.in","r",stdin);
//	freopen("M.out","w",stdout);
	int len;read(len),read(v);
	for (int i=1;i<=len;i++)
	{
		int x,w,t;read(x),read(w),read(t);
		int d=1;
		while (d<=t)
		{
			n++;
			a[n].s=d*x;
			a[n].w=d*w;
			d*=2;
		}
		a[n].s=x*(t-d/2+1);
		a[n].w=w*(t-d/2+1);
	}
	for (int i=1;i<=n;i++)
	{
		for (int j=v;j>=a[i].s;j--)
		{
			f[j]=max(f[j],f[j-a[i].s]+a[i].w);
		}
	}
	int ans=0;
	for (int i=0;i<=v;i++) ans=max(ans,f[i]);
	printf("%d",ans);
//	fclose(stdin);fclose(stdout);
	return 0;
}

例题:宝物筛选

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值