Re: 从零开始的 dp 学习笔记
你说的对,但是……
前言
太拉了,菜就多练。
在这里就没人看得到我是芙宁娜的狗了
线性 dp
[USACO1.5] [IOI1994]数字三角形 Number Triangles
最经典的题。从中可以领悟出动态规划的本质。
首先,一切 \(dp\) 题都可以用搜索写,复杂度大概是以二为底或者阶乘。这是一个保守的方案,相当于记录全部信息。
而事实上,我们并不需要这么多信息,更多时候只是特殊的那几个,我们称这些需要的信息为后效性。
后效性可以是数组下标,也可以是值。
在本题中,我们并不需要前面走的路径的所有信息,只需要了解两个上部分的答案就行了。因此,我们设状态 \(f_{i,j}\) 表示到点 \((i,j)\) 的最大权路径的权和。
然后,我们需要转移,就是把一个问题的答案从另一个问题转移过来。所以需要状态转移方程。本题里状态转移方程为:
这道题不太需要实现。
[NOIP1999 提高组] 导弹拦截
\(LIS\) 类问题。如果一个数想要加在一个数后面,只需要比前面那个数大即可。后效性在前面那个数的大小,可以设状态 \(f_{i,j}\) 表示到第 \(i\) 个数时以 \(j\) 结尾的 \(LIS\) 长度。显然每次添加只会改一个 \(j\),所以
这里需要三个优化,不是目前的范围:
-
发现 \(i\) 只依赖 \(i-1\),可以把原方程里的 \(i\) 舍弃,称之为滚动数组优化。
-
发现 \(j\) 的比较只与大小有关,使用可以考虑缩小值域并保持原本顺序,称之为离散化。
-
发现是求前缀 \(\max\),可以使用线段树或者树状数组等数据结构优化。
复杂度 \(O(n\log n)\)。本题还涉及 \(Dilwith\),在此按下不表。
P3336 [ZJOI2013] 话旧
题意:对于函数 \(f\),\(f_0=f_n=0\),图像中若下降则一定下降到 \(0\),给你 \(k\) 个点,求方案数和可能的最大 \(f_i\)。
先考虑后效性,首先要记得就是到第 \(i\) 个点是上升还是下降。我们称其分别为“上升点”和“下降点”。
接下来分几种情况讨论:
空接情况
即两点连接不会下降到 \(0\)。
这里又分三种情况:
-
直线上升,判断是否可以拼接,可以的话两个点都符合上升条件即可。(\(0\) 点特判)
-
直线下降,同理判断是否可拼接,然后统计答案就行。
-
先升后降,因为保证有解,这里就不判断是否可拼接了,对应状态即可。(\(0\) 点特判)
注意到空接情况连接方式唯一,不会多算。
地接情况
即两点连接下降到 \(0\)。
容易发现下降点下降轨迹固定,上升点上升轨迹固定,最后会在中间留出至少 \(len=x_2-x_1-y_1-y_2\) 的自由区域,里面只要符合条件可以随便放。当然运行接地的前提条件就是 \(len\ge 0\)。
第一种情形就是前面的点下降,后面的点上升,这时直接抵满 \(len\) 计算方案数即可。这里的方案数可以考虑计数 \(0\) 点,因为容易发现一个 \(0\) 点只有翻上去和抵下来两种情况。总共 \(\frac{len}{2}-1\) 个 \(0\) 点,所以方案数 \(2^{\frac{len}{2}-1}\)。
第二种是两个点都上升,看起来方案数多了不少,很难算。其实可以把第一个上升点落地的 \(0\) 点算上,方案数就显而易见了,多一个 \(0\) 点即 \(2^{\frac{len}{2}}\)。这里如果把 \(1\) 算作上升点会算重,故开始时把 \(1\) 算作下降点。
剩下两种是后一个点下降的情况,发现虽然 \(len\) 长了,但最后的 \(0\) 点少了,所以情况没变。只是注意特判 \(0\) 点。
注意这里有一个地方会算重,当 \(len>0\) 时全部 \(0\) 点往上翻就是空接情况,所以注意不要重复算。
于是解决了方案数,而对于最大值,显然就是尽量往上升。稍微解一下方程得最值 \(\frac{x_2-x_1+y_1+y_2}{2}\)。
That's all.
P3558 [POI2013] BAJ-Bytecomputer
题意:对于数组 \(a\) 有 $a_i \in -1,0,1 $,每次操作 \(a_i \longleftarrow a_i+a_{i-1}\),求最小操作数使得数组单调不降,有判无解。
作为一道 dp,这是十分不好想的。
模拟一遍,发现既然要改就不会改的比 \(-1\) 小或者比 \(1\) 大。这是一个关键点,启示我们值域不变。
再想后效性。不降序列的后效性十分明显,就是所有操作完最后的数。
自然设出状态:\(f_{i,j}\) 表示前 \(i\) 个数末尾是 \(j-1\) 的方案数。
转移分讨即可。
P4158 [SCOI2009] 粉刷匠
题意:\(n \times m\) 的格子,粉刷 \(T\) 次横着连续段,重复或不粉刷算错误,求最多能正确几个。颜色只有两种。
又一道神题。
一步步来。dp 的一个重要思想就是使问题互不干扰。本题不会竖着涂,故横条间的后效性独立,这启示我们分别 dp。
先考虑横条。考虑后效性,如果我们涂一个区间,为了使贡献不重,应该拆左右。使用扫描线,就不用管后面了。状态就显然了,\(f_{i,j,k}\) 表示第 \(i\) 个横条,刷了 \(j\) 次,前 \(k\) 个格子的最多正确数。
剩下的都很显然了。
P5301 [GXOI/GZOI2019] 宝牌一大堆
开坑,不写。
背包 dp
P1048 [NOIP2005 普及组] 采药
01 背包板题。
考虑背包问题的本质,就是分配空间维度。
令 \(W_i(V)\) 为分配给第 \(i\) 个物品 \(V\) 空间所能获得的价值。分配给每物品,再合并即可。
参考树形 dp 二叉合并,显然背包问题是可合并的,将问题转换成分配给前 \(i-1\) 个和分配给第 \(i\) 个。
所以一般背包 dp 的思路就是:递推,枚举前 \(i\) 个物品用的空间,枚举前 \(i-1\) 个物品用的空间,复杂度 \(O(nm^2)\)。注意,背包有单调性,空间越大装的一定越多。
接着考虑 01 背包。01 背包有性质 \(W_i(V)=[V\ge v_i]\times w_i\)。由背包单调性可知,空间要么不给,要么给刚好 \(v_i\) 最优,因此省去枚举 \(i-1\) 的维度,复杂度 \(O(nm)\)。
01 背包的滚动数组优化,发现倒序枚举即可避免重复。
另,01 值域背包的转移形式十分适合 bitset 辅助转移。
P1757 通天之分组背包
分组背包。如果理解了背包的本质就比较容易了。先对每一组做一遍维护对应 \(w\),然后再合并即可。