【学习笔记】dp
非常重要的一件事就是,在最值统计 dp 中,你的转移没有必要全部正确,只要保证其中错误的转移得到的答案仍然比最后要得到的答案劣即可。这种思想能很大程度上简化 dp 式子的复杂度。
决策单调性优化
感谢一下这篇,真心写得好吧。这部分笔记基本按照上面的东西来写的。
目前只知道应用于形如 \(f_i=\min\{g_j+w(j,i)\}\) 的式子。要求 \(g\) 是常数或者一个满足决策单调性的数组或者 \(f\) 本身,并且 \(w(j,i)\) 满足四边形不等式,则 \(f\) 满足决策单调性。应用决策单调性还需要 \(g\) 和 \(w(j,i)\) 可以较快计算出来保证复杂度。记 \(p_i\) 表示 \(f_i\) 取到最值的 \(j\),若对于任意 \(i<k\) 都有 \(p_i\le p_k\) 则称 \(f\) 满足决策单调性。
一般的证明方法是感性理解(大胆猜测)或者大力把转移点打出来瞪眼观察。因为证明一般很难。
四边形不等式
对于函数 \(f(x,y)\),若其对于任意 \(l_1\le l_2\le r_2\le r_1\) 有 \(f(l_1,r_2)+f(l_2,r_1)\le f(l_1,r_1)+f(l_2,r_2)\),我们称其满足四边形不等式。简记为交叉不大于包含。
证明若 \(f_i=\min\{g_j+w(j,i)\}\) 中的 \(w(j,i)\) 满足四边形不等式,则 \(f\) 满足决策单调性:
-
考虑反证。假设有 \(p_x<p_y<y<x\)。
-
根据定义有 \(f_x=f_{p_x}+w(p_x,x)\le f_{p_y}+w(p_y,x)\)。
-
根据四边形不等式有 \(w(p_x,y)+w(p_y,x)\le w(p_x,x)+w(p_y,y)\)
-
两式相加得 \(f_{p_x}+w(p_x,y)\le f_{p_y}+w(p_y,y)\)。意为 \(p_x\to y\) 比 \(p_y\to y\) 更优,与题设不符。
所以说如果求最大值四边形不等式的小于等于就要改成大于等于才满足决策单调性(存疑。
分治写法
如果 \(f_i\) 的转移和之前的 \(f\) 值无关(用另一个东西转移,比如说多层 dp 中的上一层)可以考虑把所有 \(f\) 按照一种整体二分的分治思路处理。
具体地定义分治函数 solve(l,r,L,R) 表示现在处理的是 \([l,r]\) 的 dp 值,并且已知这些位置的 \(p\) 值在 \([L,R]\) 范围内。考虑暴力 \(O(R-L)\) 得到 \(dp_{mid}\) 和 \(p_{mid}\) 然后分治左右两块,solve(l,mid-1,L,p[mid]) 和 solve(mid+1,r,p[mid],R)。复杂度是很经典的分治复杂度 \(O(n\log n)\)。但是要保证得到 \(dp_{mid}\) 的复杂度能够接受。
其实大多数情况是能够接受的。你甚至可以用 指针暴力跳区间 在正确的复杂度内得到答案。口胡的证明:可以考虑刻画指针的移动路径。一次分治内左端点从一开始的位置移到 \(L\),然后到 \(R\)。这期间右端点没有变。考虑往左边分治时指针从 \(R\) 移回 \(L\) 进行接下来的行动,然后往右边分治的时候左端点本来就在 \(R\)。所以左端点的移动是 \(O(R-L)\) 的。而往左边分治时右端点只会左移,从左边的分治往右边走时移动次数不会超过 \(R-L\),所以右端点的移动也是 \(O(R-L)\) 的。
二分队列
注意到分治无法处理 \(f_i=\min{f_j+w(j,i)}\) 之类的式子。此时就只能使用二分队列,而二分队列的适用性比分治写法更高。
维护一个决策点队列 \(q\),其中存储三元组 \((i,l,r)\),代表目前区间 \([l,r]\) 的最优决策点为 \(i\),由于相邻项满足 \(r=l+1\),实现时只需要维护 \(l\) 或 \(r\) 即可。查询时如果越过了队头区间弹出队头即可。加入新 dp 值时二分队尾元素找到一个分割点,如果这个新 dp 值把队尾整个占据了就要弹出队尾对新的队尾重新找分割点。如果新增元素本身比队尾还劣就根本没必要加进去了,如果不加这个特判按照本人二分写法会挂。
复杂度显然是 \(O(n\log n)\)。
斜率优化
目前只知道应用于对前面的一些 dp 值取 max/min 转移时使用。
一种比较通用的是如果 \(i\) 从 \(j\) 转移来的转移式能够化为与 \(i\) 相关的某项和与 \(j\) 相关的某项的乘积加上与 \(j\) 相关的某项再加上与 \(j\) 无关的项,会发现前面三个组成一个形如 \(kx+b\) 的一次函数形式,将与 \(j\) 有关的项当做 \(k,b\),查询 \(x=\) 与 \(i\) 有关的项时的最值。然后套上李超树进行转移。比较典型的例子有(随便举一个)\(dp_i=\min\{a_i\times(dp_j+b_j)+w(j)\}+s\),就可以把 \(dp_j+b_j\) 当做 \(k\),\(w(j)\) 当做 \(b\),然后查询 \(x=a_i\) 时的极值。
感觉适用范围很广,在做过的为数不多的斜率题里面都是可以用李超树优化的。而且板子会背也不难写。
注意到李超无论如何复杂度还是带了 \(\log\) 的。在 某些情况下 我们需要 \(O(n)\) 的算法。虽然李超常数很小比线性大不了多少的说。
一般的做法中是假设有 \(j<k\) 且 \(k\) 转移比 \(j\) 更优,通过移项表示成类似斜率大小比较的东西,将包含的东西转化成二维平面的点维护凸壳之类的。在某些特殊情况比如一些东西有单调性可以用单调队列维护凸壳使得复杂度降为 \(O(n)\)。
局限性很大而且不如李超无脑。优点在于一些情况下可以做到线性然后比李超稍微快一点点。其次就是这个是线性空间的。\(n\) 比较大,值域大且无法离散化的时候 出题人就要失去一些东西了 就只能写这种队列转移了。
当然对于一些不满足单调性无法用单调队列维护凸壳的时候可以 用单调栈什么的维护凸壳然后在单调栈上面二分,但这样复杂度就和李超一样了。
凸优化
wqs 二分
考虑一种问题:现需要你选择一些事物,满足其中恰有 \(m\) 个规定物品,问最大最小代价。举个例子,有 \(n\) 个可正可负的整数 \(a_i\),要你选择 \(m\) 个使得和最大。
设 \(f(x)\) 表示选择 \(x\) 个数的最大和,描点 \((x,f(x))\) 后显然围成的图形形成了一个上凸壳(下凸壳同理)。
如果我们用一个斜率去交一个凸壳,那么会有该斜率在凸壳的切点 \((p,f(p))\) 形成的直线的纵截距最大(下凸壳为最小)。此时对于任意一个凸壳上的点,它的纵截距为 \(f(x)-kx\)。
思考一下这代表什么。如果把 \(f(x)-kx\) 看做是将选择的规定物品的价值减去 \(k\),那么与切点 \((p,f(p))\) 形成直线纵截距最大的意义就是在 \(x\) 取 \(p\) 时得到问题的答案最大。
于是我们将所有规定物品的价值都减去 \(k\),此时所有的 \(f(x)\) 都会变为 \(f(x)-kx\),而此时的最值会在 \(x=p\) 处取到。
发现我们通过给定一个斜率常数的方式把选择物品个数的限制消掉了。而凸壳上的斜率都是单调的,我们显然可以二分这个斜率 \(k\),记录此时答案的同时记录得到这个答案选择了多少个物品,决定下一次二分的走向。
二分边界设成斜率的最大最小值即可,一般的题目中这个值会很大,此时要特别关注二分贡献的 \(\log\) 也会随之增大。
考虑一种特殊情况,\(x=m\) 的点左右的斜率相等,此时无论如何都切不到 \((m,f(m))\) 这个点。
于是我们考虑二分的是最后一个使得选择的个数小于等于 \(m\) 的位置,此时这个斜率一定和 \(m\) 点左右的斜率相等,于是我们得到的纵截距是正确的,直接手动把对应的 \(kx\) 加上即可。这个时候需要你在 check 里面做到尽可能多地选择数,理想情况是选择数的个数带有决策单调性不需要额外讨论,但是需要注意可能出现需要额外开个东西转移的情况。
被卡了想想看是不是这种特殊情况没有处理好。
意思就是说对于序列划分求最小花费问题,若一段的价值函数满足四边形不等式,则设分 \(k\) 段时最小花费为 \(f(k)\),则 \((i,f(i))(i\in [1,n])\) 构成一个下凸壳。此时利用 wqs 二分以及决策单调性优化可以做到 \(O(n\log n\log V)\) 或 \(O(n\log V)\),取决于决策单调性可以做到的复杂度。
闵可夫斯基和
题挺少的根本找不到几道。
现在我们有两个凸且凸性相同的函数 \(f,g\),现在我们要得到一个新的函数 \(h_{i+j}=\max/\min\{f_i+g_j\}\)。我们根据凸性考虑这个东西的几何意义。若令 \(g_1\) 与 \(f_i\) 重合,则此时的 \(g_j\) 代表的坐标即为 \((i+j,f_i+g_j)\)。注意到这个东西和合并两个凸包的要求简直一模一样。于是我们可以用闵可夫斯基和的形式 \(O(n+m)\) 地合并这两个函数得到 \(h\)。同时根据两个凸包(壳)合并后仍是凸壳我们知道 \(h\) 也是一个具有同样凸性的函数。
至于如何合并,由于我们保证合并的仅是上或下凸壳,而其有一个优美的性质即为斜率递增,我们可以直接维护函数的差分数组 \(f'_i,g'_i\),这两个数组有单调性,可以直接按照单调性归并排序实现闵可夫斯基和。需要调用时将 dp 数组还原即可。
对于一种很经典的闵可夫斯基和优化 dp 问题:给你一个序列,每次选择一段长为 \(k\) 的区间覆盖得到一个代价,问对于 \(i=1,2,\cdots,\lfloor\frac{n}{k}\rfloor\),选择恰好 \(i\) 段的代价最值。
设 \(f_{l,r,i,j,k}\) 表示考虑区间 \([l,r]\),限制左端点至少 \(i\) 个不选,右端点至少 \(j\) 个不选,恰好选择 \(k\) 段的代价最值。这个东西很容易观察到大概率是凸的。因为在你把所有最优段选完后你再选其他段会导致贡献减少。当然这个跟题目中具体价值有关。由于区间考虑和合并 dp 数组的原因此时的转移需要利用分治,为节省空间可把 \([l,r]\) 用编号表示。
由于这个函数是凸的,考虑直接用 vector 存储,把最后一维去掉。转移首先是中间不选,\(f_{[l,r],i,j}=f_{[l,mid],i,0}+f_{[mid+1,r],0,j}\),这里的加和即为凸壳合并。然后枚举中间选的段位于左部的长度 \(k\),则应该合并 \(f_{[l,mid],i,k},f_{[mid+1,r],K-k,j}\)。设这个合并完的是 \(g\),我们没有加入新增的那一段的贡献。考虑新增的这一段一定在某一个时刻被选择并在此后一直存活,因为这一段在一开始为空无法被相交替代。于是这相当于我们对 \(g\) 的差分数组在某一位插入一个值。这个东西仍然可以凸壳合并做。
由于一层凸包的大小为 \(O(\lfloor\frac{n}{k}\rfloor)\),我们的复杂度为 \(O(k^3\lfloor\frac{n}{k}\rfloor\log n)\) 也即 \(O(k^2 n\log n)\) 的。一般题目中 \(k\) 很小处于可接受范畴。
slope trick
黑科技。
考虑带有如下性质的函数:
-
是分段连续一次函数且带有凸性。
-
斜率为整数且每一段的斜率较小,绝对值总和在 \(O(n)\) 量级。
考虑用一个直线 \(kx+b\) 和一个拐点集合 \(S\) 表示其中一个凸函数。具体地,若该分段函数在 \(x=p\) 处有斜率变化为 \(\Delta k\),则我们在集合中插入 \(\Delta k\) 个 \(p\) 表示这里产生了拐。这就是 slope trick 的基本思想。
考虑两个满足上述性质的函数 \(f,g\)。现在我们希望将它们合并,即得到新函数 \(h_i=f_i+g_i\)。首先我们将维护的两条直线合并,新直线 \(l_h:(k_f+k_g)x+(b_f+b_g)\)。可以画图辅助理解。显然此时于起始点两函数完成了合并。然后我们观察到对于后面的点合并后其新斜率即为原来此处的斜率和,也就是我们只要直接合并拐点集合就可以表示出新直线的斜率变化量。
对一个满足该性质的函数取前后缀最值即将斜率大于或小于 \(0\) 的部分舍去。取该函数最值即找到斜率为 \(0\) 的段。
具体实现需依照题目观察函数性质设计。
连续段 dp/插入 dp
很有意思的 dp。
如果你的限制什么的只跟相对大小有关,那么当前 dp 的时候可以考虑状态为当前相对大小为多少而非最终值,所以转移时可以枚举新的相对大小插入进去。
另外一些情况是你考虑数列中的数构成了值域上的连续段或者按值域插入的过程中在序列中形成了若干个连续段,考虑对这种东西 dp,转移一般分为合并连续段,加在连续段旁边,新开连续段三种讨论。
部分树形背包的 \(O(nm)\) 做法
参考资料。
直接把父亲节点背包作选择儿子的转移传给儿子作初始背包则回溯回来后儿子背包和父亲背包意义上不交,不需要卷积转移直接取 max 即可。
也可以直接在 dfs 序上 dp。
遇到的时候可以尝试一下。

浙公网安备 33010602011771号