2024-12-13 18:57阅读: 15评论: 0推荐: 0

背包 dp 学习笔记

01背包

题面

N 件物品和一个容量是 m 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

思路

定义 dpi,j 为前 i 个物品,体积为 j 的最大价值。

由此我们可以讨论取或不取,推出转移方程。

dpi,j=max(dpi1,jvi+wi,dpi1,j)

由于对 dpi 的影响只有 dpi1,那我们不妨去掉第一维,直接用 dpi 表示体积为 j 时的最大价值,所以方程为:

dpi=max(dpivi+wi,dpi)

需要注意的是,枚举时需要倒序枚举,才能保证物品只能使用一次,顺序则为完全背包做法。

for(int i=1;i<=n;i++)
{
	for(int j=m;j>=w[i];j--)//倒序枚举
	{
		dp[j]=max(dp[j],dp[j-w[i]]);
	}
}

完全背包

题面

N 件物品和一个容量是 m 的背包。每件物品可以使用无限次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

思路

根据 01 背包,我们不难想出正序枚举即可实现物品使用多次。用 dpi 表示体积为 j 时的最大价值,所以方程与 01 背包一模一样:

dpi=max(dpivi+wi,dpi)

for(int i=1;i<=n;i++)
{
	for(int j=w[i];j<=m;j++)//正序枚举
	{
		dp[j]=max(dp[j],dp[j-w[i]]);
	}
}

多重背包

题面

N 件物品和一个容量是 m 的背包。每件物品可以使用 ci 次。第 i 件物品的体积是 vi,价值是 wi。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。

暴力思路

容易想到,把「每个物品拆成 ci 个只能使用一次的物品」,再套用 01 背包的模板即可。

转移方程即为

dpi,j=max(dpi,jk×wi+k×vi)(0kci)

单调队列优化

我们在暴力思路上容易观察对于每一个 dpi,j 都是从 dpi1×widpi2×widpici×wi 转移而来的。他们第二维对 wi 的余数都相同。

那我们不妨跳着计算 dpi,j,固定一个余数 r

既然余数统一,形式就可以表示成 r+x×vi,而 x 是连续的,那对于当前的 k 我们需要求的就是 x 在区间 [kci,k]dpi1,r+x×wi+(kx)×vi 的最大值。

固定长度的区间最大值,即可用单调队列来实现。

有一个小细节就是队列里存的是 i1 的 dp 值。

具体细节见代码。

for(int i=1;i<=n;i++,b^=1)//滚动数组
{
	for(int r=0;r<v[i];r++)//固定余数
	{
		while(!q.empty()) q.pop_back();//清空队列
		for(int k=0;k<=m/v[i];k++)
		{
			while(!q.empty()&&q.front()<k-c[i]) q.pop_front();//弹队首
			while(!q.empty()&&dp[b^1][r+q.back()*v[i]]+(k-q.back())*w[i]<dp[b^1][r+k*v[i]]) q.pop_back();//维护队列递减性,注意是 b^1
			q.push_back(k);
			dp[b][r+k*v[i]]=(dp[b^1][r+q.front()*v[i]]+(k-q.front())*w[i]);//更新答案
		}
	}
}

分组背包

题面

n 件物品和一个大小为 m 的背包,第 i 个物品的价值为 wi,体积为 vi。同时,每个物品属于一个组,同组内最多只能选择一个物品。求背包能装载物品的最大总价值。

思路

这道题其实和 01 背包非常的相似,其实是从「在所有物品中选择一件」变成了「从当前组中选择一件」,于是就对每一组进行一次 01 背包就可以了。

注意循环顺序,才能保证答案正确性。

for (int k = 1; k <= zs; k++)//循环每一组
  for (int i = m; i >= 0; i--)//循环背包容量
    for (int j = 1; j <= cnt[k]; j++)//循环该组的每一个物品
      if (i >= w[z[k][j]])
        dp[i] = max(dp[i],dp[i - w[z[k][j]]] + c[z[k][j]]);//像 01 背包一样状态转移

求字典序最小方案

题面

N 件物品和一个容量是 m 的背包。每件物品只能使用一次。
i 件物品的体积是 vi,价值是 wi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出字典序最小的方案。这里的字典序是指:所选物品的编号所构成的序列。物品的编号范围是 1N

思路

在背包求解的基础上,新建一个数组记录是「从哪一个转移过来」的就行了,注意不能压维,不然无法判断具体是从哪一个转移过来的。

要倒着枚举,保证字典序最小。

for(int i=n;i>=1;i--)
{
	for(int j=0;j<v[i];j++)
	{
		dp[i][j]=dp[i+1][j];
		path[i][j]=path[i+1][j];
	}
	for(int j=v[i];j<=m;j++)
	{
		if(dp[i+1][j]>dp[i+1][j-v[i]]+w[i])//注意如果相等尽可能取当前数
		{
			dp[i][j]=dp[i+1][j];
			path[i][j]=path[i+1][j];
		}
		else//因为倒着枚举,当前数字典序更小
		{
			dp[i][j]=dp[i+1][j-v[i]]+w[i];
			path[i][j]=i;
		}
	}
	cout<<dp[1][m]<<" ";
	int x=path[1][m];
	s.push(x);
	m-=v[x];
	do{
		x=path[x+1][m];
		m-=v[x];
		s.push(x);
	}while(path[x+1][m]);
	cout<<s.size()<<"\n";
	while(!s.empty())
	{
		cout<<s.front()<<" ";
		s.pop();
	}

本文作者:yaaaaaan

本文链接:https://www.cnblogs.com/yaaaaaan/p/18605625

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   yaaaaaan  阅读(15)  评论(0编辑  收藏  举报
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑
点击右上角即可分享
微信分享提示