背包

本文深入讲解了背包问题的几种变体,包括01背包的动态规划解法、正序与倒序的重要性,完全背包的初始化策略,多重背包的高效求解技巧,以及分组背包的处理方法。通过实例和滚动数组优化,揭示了背包问题的核心思想和技巧。

背包

背包是线性DP中特殊的模型,我们把他单独拿出来进行学习

01背包

有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中
每种物品都只有一件
根据我们所学的线性DP的知识,我们很容易想到依次考虑每一个物品是否放入背包,同已经“处理的物品的数量”,作为DP的阶段,用“背包中已经放入的物品总体积”作为维度,f[i][j]表示从i的物品放入体积为j的物品,物品的最大价值和
f[i][j]=max(f[i-1][j]不选第i物品,f[i-1][j-v[i]]+wi选)
---------------重新------------

学习背包之前,也就是学习动态太规划之前,我们要 明白一个道理,要使得价值最大的话,为什么不能用贪心来搞
我们知道,贪心和dp是两个完全不同的东西,这两个可以说是对立的 ,因为背包这种题,在价值的基础上还有空间的限制,所以我们需要两个都考虑
至于背包,还有一个非常吓人的东西,他需要从后往前进行更新
1.正序为啥不行:dp[4]=dp[3]+v[2]
dp[5]=dp[4]+v[2]
将dp[4]带入dp[5]可以得到dp[5]=dp[3]+2*v[2]
即会使用到2个2号物品,不满足0-1背包的要求
即每个物品只使用一次,所以这样会重复使用,就跟递推一样,一个覆盖一个,所以会重复
这就是背包的精髓
2.倒叙为什么可以:
因为我们使用的都是破旧的,比如说我们更新f[5]时需要用到f[4]这个时候f[4]还没有更新,然后到了f[4]需要使用的时f[3]所以这样一步步,不会考虑到覆盖现象
所以这个题有点类似于在沙滩上倒着行走,不会看到脚印,不会才到原来的脚印,因为f[i]只会与之前的f[i~1]有关系

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

听过DP的方程我们可以发现,每一个状态都和之前的状态有关系,我们在这种情况需要使用滚动数组进行优化
然后我们看看数组,就会明白,实际上我们只会运用到一维数组,所以我们直接可以省略一维
还需要注意,我们在放背包的时候使用了倒序循环,这是重点,因为我们在放背包的时候,从后往前会满足DP守则,满足01背包的原则,不会重复放,每个物品只会放一次

滚动数组

是用来优化动态规划算法,简单的进行理解就是让数组进行滚动,固定几个存储空间,来达到压缩,节省空间的作用,主要应用在线性DP中比如背包,我们常常需要得到一个连续的解,所以我们没有必要非得求出来前面的,比如01背包

int f[2][size+1]
memsmet(f,-inf,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++)
{
	for(int j=0;j<=m;j++)
		f[i&1][j]=f[(i-1)&1][j];
	
	for(int j=v[i];j<=m;j++)
	{
		f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j-v[i]]+w[i]);
	}
}

i&1只会有两种情况,0或者1,也就是动态规划的状态只会再dp[0][]和dp[1][]交替,以至于省下很多空间

完全背包

n个重量和价值分别为Wi,Vi的物品,现从这些物品中挑选出总量刚好为 W 的
物品,求所有方案中价值总和的最大值
类如:
n=3 w=4
Wi=1 Vi=2
Wi=2 Vi=5
Wi=2 Vi=1
像这种方案最大的价值就是6,方案是第二件加第三件物品,但是明明第一件加第二件为7不大于6吗?因为第一件加第二件不恰好装满
对于完全背包主要在于dp数组的初始化
恰好装满背包的时候:需要进行初始化,f[0]=0其他的f全部初始化为负无穷大
不需要恰好装满背包的时候:初始化全部为0
f[i,j]=max(f[i-1,j],f[i,j-vi]+wi)
方程的意义就是选或者不选第i种物品
对于背包的正序倒序循环,会和背包的数量有关系,01背包因为最多有一件所以必须倒序循环,但是完全背包有无数,需要正序循环

int f[size+1];
memset(f,-inf,sizeof(f));
f[0]=0;
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]);
int ans=0;
for(int j=0;j<=m;j++)
	ans=max(ans,f[j]);

多重背包

给定N种物品,其中第i种物品的体积为Vi,价值Wi,并且有Ci个,有一个容积为M的背包,要求选择若干个物品放入背包,是的物品总体积不超过M的前提,价值最大
和完全背包的不同就是个数限制
求解多重背包直接方法就是把第i个物品看作是Ci独立的物品,然后转化为01背包进行计算,也就是说Ci*Ci次运算,但是这种方法的效率过低

unsigned int f[size+1];
memset(f,-inf,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)
{
	for(int j=1;j<=c[i];j++)//控制个数
	{
		for(int k=m;k>=v[i];k--)
		{
			f[k]=max(f[k],f[k-v[i]]+w[i]);
		}
	}
}
int ans=0;
for(int i=0;i<=m;i++)
{
	ans=max(ans,f[i]);//统计答案
}

分组背包

分组背包,就是将物品分组,每组的物品相互冲突,最多只能选一个物品放
进去
这类题就是从所有物品变成了当前组于是就是好几组01背包
因为最多选一件所以i我们自然而然就能利用阶段,设f[i,j]表示从i组选出总体积为j的物品放入,物品最大价值的和
注意搞清楚组号,容量,已经方程

for(int k=1;k<=tt;k++)//枚举每一个组
{
	for(int i=m;i>=0;i--)
	{
		for(int j=1;j<=cnt[k];j++)
			if(i>=w[t[k][j]])//表示t[k][j]表示第k组第j号物品
			{
				f[i]=max(f[i],f[i-w[t[k][j]]]+c[t[k][j]]);
			}
	}
}

依赖背包

这种背包问题,其实就是选第i件物品,就必须要第j件物品,并且保证不会 循环引用 ,这就是有点像多叉树的形式,所以将不依赖别的物品叫做主件 (也就是i物品),将依赖主件物品的叫做附件(也就是j物品)
对于包含主件和附件集合有如下可能,选择主件再选择一个附件,选择主件后选择两个附件…需要将以上可能的容量和价值转换成一件件物品,因为只选一种,所以可以看成分组背包
如果变成多叉树的集合,要先算算子节点集合,最后算父节点集合,也就是往上递归

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值