首页 > 其他分享 >背包(笔记合集)

背包(笔记合集)

时间:2024-04-20 10:11:32浏览次数:24  
标签:方案 背包 int max 笔记 合集 转移 AcWing

一般代码只是例子,具体使用依据题目来, DP是一种思想,代码都以属性为最大值等等为例子

01背包

基本的背包
简单说就是有n个物品和容量为m的包,求其max/min/方案数等等即属性
一般转移方程为f[i][j]意思为在前i个里容量为j的情况下的要求的属性
(可忽略)一般这里的转移是在f[i][j],第i个数取与不取
时间复杂度O(n*m)

一般代码

for (int i = 1; i <= n; i ++ ) // 枚举当前几个物品
    {
        int v, w;
        cin >> v >> w;
        for (int j = 1; j <= m; j ++ )
        {
            f[i][j] = f[i - 1][j];
            if (j >= v) f[i][j] = max(f[i][j], f[i - 1][j - v] + w);
        }
    }
一维优化
for (int i = 1; i <= n; i ++ ) // 枚举当前几个物品
    {
        int v, w;
        cin >> v >> w;
        for (int j = m; j >= v; j -- ) // 体积
            f[j] = max(f[j], f[j - v] + w);
    }


必须倒序枚举体积,我们的f[j - v]用的实际是f[i - 1][j - v], 而正序枚举我们可能会先更新了
f[j - v]使得后面用到f[j - v]时实际用的是f[i][j - v]从而错误, 只要倒序就可以解决此问题

完全背包

即有n种物品,每种无限个
一般的转移方程为 f[i][j] = max(f[i - 1][j], f[i][j - v]);

优化代码

O(n*m)

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

O(n*m*k)

for (int i = 1; i <= n; i ++ ) 
    {
        int v, w;
        cin >> v >> w;
        for (int j = 1; j <= m; j ++ )
            for (int k = 0; k <= j / v; k ++ )
                f[i][j] = max(f[i][j], f[i - 1][j - v * k] + w * k);
    }
方程推导

正常的思路:
f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w, f[i - 1][j - 2v] + 2w,...,f[i][j - kv] + kw)
k∈(0, j/v);
这种复杂度过高,一般接受不了

一般的方程f[i][j] = max(f[i - 1][j], f[i][j - v]);
f[i][j - v]如何得来? (注意max中的对应)
f[i][j] = max(f[i - 1][j], f[i - 1][j - v], f[i - 1][j - 2v],...,f[i][j - kv])
########f[i][j - v] = max(f[i - 1][j - v], f[i - 1][j - 2v],...,f[i][j - kv])
k是相同
可得f[i][j] = max(f[i - 1][j], f[i][j - v]);
加上价值就是f[i][j] = max(f[i - 1][j], f[i][j - v] + w);

一维优化
for (int i = 1; i <= n; i ++ ) 
{
	int v, w;
	cin >> v >> w;
	for (int j = v; j <= m; j ++ )
		f[j] = max(f[j], f[j - v] + w);
}

这里只能正序枚举,和01背包相反,因为我们转移的实际上是f[i][j - v],正序可以提前更新f[j - v]使他实际上成为f[i][j - v]符合要求倒序则为f[i - 1][j - v]不合要求

多重背包

有n种物品但是每种物品有数量限制

朴素代码
for (int i = 1; i <= n; i ++ )
{
	int w, v, s;
	cin >> v >> w >> s;
	for (int j = 0; j <= m; j ++ )
		for (int k = 0; k <= min(s, j / v); k ++ )
			f[i][j] = max(f[i][j], f[i - 1][j - k * v] + k * w);
}
二进制优化

把物品的个数S拆成一堆数,形成一个单独的物品,最后采用01背包去做
一般采用二进制的方法
因为一堆二进制数加起来可以加成任何数
如0001,0010,0100,1000这四个二进制数相加,可以组成任何小于等于1111的数(正数)

for (int i = 1; i <= n; i ++ ) 
{
	int v, w, s, k = 1;
	cin >> v >> w >> s;
	while (k <= s)
	{
		V[ ++ cnt] = v * k;
		W[cnt] = w * k;
		s -= k; // 这样拆是logn个比较高效
		k <<= 1; 
	}
	if (s > 0) // 我们减到最后可能会有剩下的,也加上
	{
		V[ ++ cnt] = v * s;
		W[cnt] = w * s;
	}
}

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

一般够用

单调队列优化

口诀:比你小还比你强,你就出列了~~~
时间复杂度为O(nm)
因为一般m都比较大所以常用[[DP的通用优化#滚动数组]]

代码
for (int i = 1; i <= n; i ++ )
{
	int w, s, v;
	cin >> v >> w >> s;
	memcpy(g, f, sizeof f);
	for (int j = 0; j < v; j ++ )
	{
		int hh = 0, tt = -1;
		for (int k = j; k <= m; k += v)
		{
			if (hh <= tt && q[hh] < k - s * v) hh ++ ;
			while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;
			q[ ++ tt] = k; 
			f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w);
		}
	}
}
思路

首先你要知道在s个物品限制下转移方程为(一般情况s >= m / v)
这里推荐看我以前的笔记

	f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w, f[i - 1][j - 2v] + 2w, ... , f[i - 1][j - sv] + sw);
    f[i][j - v] =          max(f[i - 1][j - v],     f[i - 1][j - 2v] + 1w, ... , f[i - 1][j - sv] + (s - 1)w, f[i - 1][j - (s + 1)v] + sw);
    f[i][j - 2v] = ...
    f[i][j - 3v] = ...
    ...
    r = j % v;
    f[i][r + v] = max(f[i - 1][r + v], f[i - 1][r] + w)
    f[i][r] = f[i - 1][r]
    
    经典的图啊
    根据完全背包优化的思路我们可以得到上面的图(也可以看我过去笔记的图片)
    我们能发现一个事情,因为有s的限制,在一般情况下,我们没法直接用f[i][j - v]来转移
    but我们可以从它的余数出发, f[i][r], 这个是值固定的,往上f[i][r + v]看图
    越往上我们会发现,在他们的max内的第一个都在增加v(r + v), 且当s > j / v时它就开始滑动(像f[i][j], f[i][j - v], max的数量一样,但是f[i][j]
    向前进了1个v, 且w的值都加了1w, 除了新进来的)
    这就想到了单调队列,可以靠它维护这个区间最大值,优化的点,保持队列队头最大,利用这个最大,优化计算量;并且可以计算滑动窗口内的最大值
    看看代码,多想想
    
    因为过于复杂还是看y的课比较好。

以前的笔记
如果你还有记忆的话应该会懂得
一般比二进制优化快

二维费用背包

很简单就是有两个费用,多开一维,记录费用即可非常简单
状态一般为 f[i][j][k]在前i个数内,在两个费用都不超过j和k的情况下的属性

可以和上面三种背包结合,相当于前缀

一般代码(二维费用01背包)
for (int i = 1; i <= n; i ++ )
{
	int w, v, t;
	cin >> v >> t >> w;
	for (int j = m; j >= v; j -- )
		for (int k = p; k >= t; k -- )
		{
			f[j][k] = max(f[j][k], f[j - v][k - t] + w);
		}
}

不少于问题

之前没整理到这个
简单说就是对于费用不少于的问题,这也可以和上面三种背包连用,相当于前缀

对于这个直接DP分析即可,关于不少于,直接设f[i][j]为在前i个里面,费用不少于j的情况
那么第i个可选可不选,不选就是f[i - 1][j],选的话是f[i - 1][j - v] 这时候可能会有j < v的情况,这时候也要转移,因为它符合,费用不少于j(大于了当然可以),写成这个就行f[i - 1][max(0, j - v)]。从相当于从0直接到j。

这时候回头想想,好像之前的当j < v的时候就不转移了(可看看上面代码),这是因为之前状态是费用最大为j,这就是本质的差距。

恰好问题

这是一类小问题,状态设计要改变为,恰好,转移没什么变化,但初始化有变动,一定要先设置为不可能情况,因为对于一种状态
像有许多石头,价值和重量无关,求恰好重量为j时的最大价值,石头有(前面是价值,后面是重量){1, 1},{2, 1},{2, 2}

当f[2][3]转移的时候,f[1][2]这种情况是不可能的,所以f[2][3] = max(f[1][2] + 2)
整个转移是转移不了的,但是价值不大于j时是可以的。这就是它的本质

求方案数

这类其实有点技巧,最稳妥的方法是,把题目改为恰好,这里用一个例题来说明。
求最优方案数
AcWing 11. 背包问题求方案数 - AcWing
题中要求不超过j方案最大价值的方案数,
有两种大方向

  1. 利用不超过直接求解,因此f初始化都为0,对于每种情况f[i][j]最少有1种方案,然后转移时如果可以更新就直接更替cnt,相等就加上cnt,最后直接输出cnt[n][m]即可
  2. 利用恰好,把不超过j,改为恰好为j,转移和上面一样,然后在f[n][j]中记录,最大值,并把所有的最大值相等的方案加上。
    如上代码在上面链接中
    因此对于一个问题,除了特殊记录以外,就可以直接转移或利用恰好凑出方案

而转移时如果可以更新就直接更替cnt,相等就加上cnt,这是所有方案问题的最基本的思想,不限于动态规划

关于方案的保存

问题在保存选的方案,像这个AcWing 1013. 机器分配 - AcWing比较典型,除了求解答案,还要输出每个工厂选择的机器数,可以dfs,可以像我这样原路推回去(实际上更多使用这种),
没有固定的方式,这里只说明有此类问题。

枚举的状态

正常背包的转移为f[i][j] = max(f[i][j], f[i - 1][j - v] + w)
我们依赖于第i - 1个物品进行转移,实际上还可以依赖体积,来转移。
思路为
{不选}
{选择体积最大为k的子集,并加上这个点} k <= j

对于正常的背包,这是没有用的,但对于树形等物件间有特殊关系的,有奇效.
AcWing 10. 有依赖的背包问题 - AcWing这题就需要对于每个子树分配体积,来计算
否则复杂度将爆炸

有依赖的问题

分为很多种,例如可能依赖是树形的,可能是线性的,等等。
中心思想就是转移好子集,不能言传。
AcWing 10. 有依赖的背包问题 - AcWing
487. 金明的预算方案 - AcWing题库
上面是较好的一道,也是较难的一道。
下面的较为简单适合入入门。

求具体方案

这种会问你最后选择的方案是是什么,而不是方案数,可能有限制,如选择字典序最小的最优选择方案是什么。这种有两种思路。

  1. 直接保存方案,利用string,或者vector,在转移过程中保存方案,通过比较方案来选择最小字典序,这种一般较慢,但很稳定。
  2. 先求出最优方案,再利用最优方案,从大枚举,或从小枚举,推出选择方案。这种较快,但边界和枚举设计需要考虑清楚。
    AcWing 12. 背包问题求具体方案 - AcWing
    具体代码如上

关于状态转移的奇技淫巧

  1. 有时候我们不能直接转移状态,而要合成状态,像f[i + 1][j + v] = ...这时候可能会有“卡壳”情况,即我们无法直接判断情况,这时候不妨从0出发把for (int i = 1; i <= n; i ++ )变为for (int i = 0; i < n; i ++ )这样“退位”,在某些时候有奇效如这题P1156 垃圾陷阱而这就是刷表法,[[刷表法和填表法]]

变化的价值/体积(泛化物品的背包)

对于一类题,它的价值和体积会随着你的选择顺序不同而改变,这时候需要考虑优先级,
常用方法

先设x,y为两个相邻物品,然后列出(如价值)的不等式,像如此价值的情况
先x后y w = (p + c[x]) * b[x] + (p + c[x] + c[y]) * b[y] (1)
先y后x w = (p + c[y]) * b[y] + (p + c[y] + c[x]) * b[x] (2)
我们如果要让x在前的话,那么(1) > (2)
可以解得 c[x] * b[y] > b[x] * c[y],然后我们利用这个性质排序,再进行背包处理

做了那么多题,对于背包,正确的物品排序确实很重要。

给出对应的例题试试吧P1417 烹调方案 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

在最值情况下的最值

这类题题意如求在最大价值1的情况下,最小的价值2为多少,即在最大价值1下的最小价值2,
这种算是比较好做,只要在转移出最大价值1的同时,记录最小价值2,更新时替换,相等时对比求最小。注意,前提是最大价值1,所以要对它Dp

例题P1509 找啊找啊找GF - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

标签:方案,背包,int,max,笔记,合集,转移,AcWing
From: https://www.cnblogs.com/blind5883/p/18147255

相关文章

  • Asp-Net-Core开发笔记:使用alpine镜像并加入健康检查
    前言使用docker部署AspNetCore应用已经是标配了,之前我一直使用mcr.microsoft.com/dotnet/aspnet:8.0这类镜像,简单粗暴,不过可以使用alpine进一步优化镜像大小。很多开源工具的docker都有健康检查,这次我顺便也给加上了。添加健康检查注册服务builder.Services.AddHea......
  • 射影几何学笔记
    给大家拉坨大的。在中学阶段,我们就研究过欧几里得平面上的几何。在初中阶段我们学习了平移与旋转,在高中阶段我们学习了仿射,这些几何变换有一个共同点:保持共线三点与共点三线在变换后仍共线或共点。然而在生活中,除了这些变换以外,还有更一般的变换也拥有这个性质:比如,我们在空中对着......
  • 高斯消元学习笔记——P304题解
    如果你觉得这篇太啰嗦问题[SDOI2006]线性方程组题目描述已知\(n\)元线性一次方程组。\[\begin{cases}a_{1,1}x_1+a_{1,2}x_2+\cdots+a_{1,n}x_n=b_1\\a_{2,1}x_1+a_{2,2}x_2+\cdots+a_{2,n}x_n=b_2\\\cdots\\a_{n,1}x_1+a_{n,2}x......
  • java spring boot 2 开发实战笔记
    本案例是java spingboot 2.2.1  第一步搭建环境:安装依赖由于我们公司项目是1.8环境不能乱,我现在自己的电脑是1.8环境,所以本次整理的boot代码也只能用1.8boot版本为:2.2.1,新建项目后,在xml文件中复制上以下代码xml配置,最精简运行起来的  需要配置一个数据库,8.0以......
  • 畅捷通T+升级笔记
    笔者因安全问题升级畅捷通T+过程失败,几处经验记录于下:不保证跨级升级的成功:在运行的低版本T+上安装高版本补丁包时,除安装文件之外,还须进行数据库升级。数据库的升级是由安装的高版本升级工具执行的,而该过程可能出错,例如尝试迁移一个不存在的数据表。笔者认为,这是由于安装的补丁......
  • day16_我的Java学习笔记 (Set、案例、Collections、Map、集合嵌套)
    1.Set系列集合1.1Set系列集系概述1.2HashSet元素无序的底层原理:哈希表JDK1.7HashSet原理解析:JDK1.8HashSet原理解析:1.3HashSet元素去重复的底层原理Set集合去重复的原因,先判断哈希值,再判断equals重写equals()和HashCode()方......
  • ROS笔记5--动作通讯
    1、动作通讯简介机器人是一个复杂的智能系统,并不仅仅是键盘遥控运动、识别某个目标这么简单,我们需要实现的是送餐、送货、分拣等满足具体场景需求的机器人。在这些应用功能的实现中,另外一种ROS通信机制也会被常常用到——那就是动作。从这个名字上就可以很好理解这个概念的含义,......
  • day15_我的Java学习笔记 (Collection、数据结构、List、泛型深入)
    1.集合概述2.Collection集合的体系特点3.Collection集合常用API1.添加元素,添加成功返回true,失败返回false2.清空集合的元素3.判断集合是否为空,是空返回true,反之为false4.获取集合的大小5.判断集合中是否包含某个元素6.删除某个元素(......
  • 【学习笔记】 字符串基础 : 后缀自动机(基础篇)
    本文只介绍关于\(\mathbf{SAM}\)的基本概念与实现后缀自动机是什么类似\(\text{AC}\)自动机,后缀自动机(\(\text{SAM}\))是能且只能接收字符串所有后缀的自动机我们首先要知道,\(\mathbf{SAM}\)是只能接收所有后缀的结构而不是只能维护后缀的结构事实上\(\mathbf{SAM}\)......
  • ROS2笔记4--服务通讯
    ROS2中话题通讯可以实现多个ROS节点之间数据的单向传输,不过话题通讯是一种异步通信机制,发布者无法准确知道订阅者是否收到消息。而服务通信是一种基于请求响应的通信模型,在通信双方中客户端发送请求数据到服务端,服务端响应结果给客户端。 从服务实现机制看这种形式是CS模型,客......