进阶动态规划学习笔记

一、数位 DP

顾名思义,数位 DP 就是一种在数位上进行的计数类 DP。


1. 基本套路

状态设计:几个维度依次为 位数、最高位填什么、一些特殊限制,表示的是一个前缀和的信息。

方程设计:一般分为两步。

  • 预处理

通过简单的 DP,得到一个 带前导 0 和一个 不带前导 0 的初始数组。

  • 拼凑

先将题目给的区间询问转换为两个前缀询问之差。然后模拟预处理的 DP 方程再进行计算。

这里注意一个叫作 “卡上界” 的技巧。大致是这样的:假如当前询问的是 566。明显在我们研究最高位填 5 时,第二位就不能填 7~9 的任何数码,但是可以任填 0~6;在我们研究最高位填 1~4 时,则又可以在第二位任填 0~9 了。因此,对于 1~4,它们的 DP 值和预处理数组的 DP 值是一样的;而对于上界 5,它的 DP 值则是经过重新计算的。对于每一位都要计算卡/不卡上界的结果。但请再次注意,末位不卡上界。

最后,将合法的状态加起来就可以了。注意 最高位不要算上前导 0


2. 小技巧

可能我比较菜吧,这玩意对我来说挺恶心的。这里总结一些可能有用的小技巧。

  • 不管是预处理还是拼凑,将末位特殊对待。

  • 可以通过检查计算结果是否为单增序列来检查算法正确性。

  • 将所给数事先拆分到数组里可以省事。


3. 例题



二、状压 DP

此内容的基本概念我们已经在上一阶段学习过了。放一个 link 在这里。

几道另外的例题:



三、树形 DP

此内容的基本概念我们也在上一阶段学习过了。link

普通例题:

  • P3177 [HAOI2015] 树上染色:不同于常规的树形 DP 以子树为单位的状态设计,这道题运用了 “费用提前计算” 的思想,每次计算这条边在整棵树中会被“覆盖”多少次。而且,树形背包严格用 size 卡上下界似乎可以做到 \(O(n^2)\)(我还没看懂证明,先咕)。

  • Tree with Maximum Cost:一道二次换根练习题。

类树结构:

  • 基环树

  • P4410 [HNOI2009] 无归岛:仙人掌最大独立集。仙人掌有两种基本方法:dfs 树和圆方树。此处选用 dfs 树。对树形部分树形,对环形部分环形即可。



四、虚树 DP

1. 基本概念

这个东西主要用于多次的树形 DP。每次询问会给你一堆点的限制,且点的总数满足 \(\sum \le n\)

明显,如果每次询问时直接再整棵树 DP 一次是相当浪费的。应当设计一种算法,只与询问点(关键点)有关。我们把建成的剔除了无用点的树称为虚树。

注意,虽然说虚树剔除了无用点,但它仍然有部分“不是关键点的点”。因为为了维护完整的结构,必须要保留关键点之间的 lca。


2. 基本操作

分为几步:预处理、建虚树、DP。

本人用得最多的构造方式为:二次排序 + lca 连边。

以下抄自 OIwiki:

  • 将关键点按 dfs 序排序。

  • 遍历,求排序后相邻两个点的 lca,并将其加入数组。

  • 对数组再按 dfs 序排序,并去重。

  • 再遍历一遍,求相邻两个点 x, y 的 lca,将 lca 与 y 在虚树上连边。

这里请尤其注意:“数组”过程中可能出现 2n 个点,要开两倍 size。


3. 几个问题

Q:

为什么要按照 dfs 序排序?这样求出来的 lca 为什么就覆盖了任意两个关键点之间的 lca?

A:

可以用 Tarjan 求 lca 的 dfs 过程来理解这个问题。如果当前递归到了子树 x,那么在退出 x 子树递归前,必须把子树 x 内的 lca 全都求完。而节点 x 本身成为一个 lca 的条件就是有两棵子树中分别有一个关键点。那么这两个关键点最简便的选择就是 某棵子树的最后一个关键点某棵子树的第一个关键点

按照 dfs 序排序,相当于就是确定了 dfs 的顺序,每次求相邻两个点的 lca,本质就是“某棵子树的最后一个”和“某棵子树的第一个”求 lca。

Q:

为什么连接 lca 和 y 就可以保证虚树连边不重不漏?

A:

如果 x 是 y 的祖先,那么 x 直接到 y 连边。因为 x 和 y 的 dfs 序是相邻的,所以 x 到 y 的路径上面没有关键点。

如果 x 不是 y 的祖先,那么就把 lca(x,y) 当作 y 的的祖先,根据上一种情况也可以证明 lca(x,y) 到 y 点的路径上不会有关键点。

——OIwiki

(虽然我觉得这个证明只说了不重性,但我实在想不出来什么情况会漏,就不证了)


4. 注意事项

  • 在推广到虚树前,最好先完整打一份 \(O(nq)\) 的暴力,保证其正确性。

  • 注意区分关键点和关键点在数组中的下标。

  • 虚树点数组记得开两倍 size。

  • 多测要清空。

  • 一般来说,我们要硬性在虚树中加入节点 1。一定要特判它的一些特殊性。


5. 例题



五、区间 DP

link



六、普通数据结构优化 DP、单调队列优化 DP

1. 基本概念

link

总结一句:实际能够用数据结构优化的 DP(不论是变量、数据结构、还是单调队列)都必须遵从一个条件——决策间的相对大小关系不会发生变化。然后,我们才能根据决策的限制选择合适的数据结构进行维护。


2. 1D/1D 动态规划

\[f[i] = \min_{L(i) \le j \le R(i)}(f(j)+val(i, j)) \]

其中 \(L(i), R(i)\) 是关于 \(i\) 的一次函数,满足单调性;\(val(i, j)\) 是一个关于 \(i, j\) 的多项式函数。

1D/1D 是两种 DP 转移优化方法——单调队列优化、斜率优化——的基础。

单调队列优化 DP 可用于的情景:\(val(i, j) = val_1(i)+val_2(j)\)。但单调队列能够优化的并非一定满足上述条件,详见下文例题。


3. 例题



七、斜率优化

1. 基本概念

斜率优化可用于的情景:\(val(i, j) = val_1(i) \times val_2(j) + val_3(i) + val_4(j)\),即 \(val(i, j)\) 可包含和 \(i, j\) 乘积相关的项。

回顾 1D/1D 的 DP 方程:

\[f[i] = \min_{L(i) \le j \le R(i)}(f[j] + val_1(i) \times val_2(j) + val_3(i) + val_4(j)) \]

暂且去掉 \(\min\),将其移项改写:

\[f[j] + val_4(j) = -val_1(i) \times val_2(j) + f[i] - val_3(i) \]

\(f[j] + val_4(j)\) 为纵坐标,\(val_2(j)\) 为横坐标,则可以发现:上式为一条过 \((val_2(j), f[j] + val_4(j))\) 的一次函数,满足斜率为 \(-val_1(i)\),截距为 \(f[i] - val_3(i)\)。可以发现我们已经将 \(i, j\) 拆解开了。

那么,对于每一个 \(i\),就是用一条斜率固定的直线依次尝试每一个点 \(j\),使得截距尽可能地小。

可以发现:

  • \(j\) 的所有可能取值在 \(j\) 形成的下凸壳(下凸包)上。

  • \(j\) 与凸壳前驱形成的线段斜率比 \(i\) 的斜率小、\(j\) 与凸壳后继形成的线段斜率比 \(i\) 的斜率大时,\(j\) 就是 \(i\) 要找的决策。


2. 注意事项

  • 斜率计算为了避免精度问题,一般将 \(\frac{y_1}{x_1} < \frac{y_2}{x_2}\) 转化为 \(y_1x_2 < y_2x_1\) 计算。但注意当 \(x_1x_2 < 0\) 时需要变号

3. 例题

  • T388391 0x5A-斜率优化dp-任务安排3T388395 0x5A-斜率优化dp-Cats TransportP3195 [HNOI2008] 玩具装箱P4360 [CEOI2004] 锯木厂选址:一般来说,横坐标/斜率都是某个位置的前缀和。这些题中,有横坐标、斜率都关于 \(i, j\) 单调递增,因此可以通过单调队列 \(O(n)\) 维护决策集合。

  • P2900 [USACO08MAR] Land Acquisition G

    这道题首先巧妙在通过排序、去除无关,得到一个 \(x\) 递增、\(y\) 递减的序列,将一个关于区间最值的 \(val(i, j)\) 转化为了仅关于区间两端点的 \(val(i, j)\)(类似前缀和那种转化方法)。

    斜率优化的方程为:\(f[j] = -x[i]*y[j+1]+f[i]\)。观察方程发现,横坐标、斜率都满足递减。但只要横坐标和斜率各自有单调性,都可以用单调队列 \(O(n)\) 维护。(当然,对于下凸壳,在二者单调性相反,且对决策集合下界没有额外限制的情况下,会退化为单调栈。)

  • P3628 [APIO2010] 特别行动队

    这道题不一样的地方是:求 \(\max\)。此时将维护下凸壳转为维护上凸壳,且斜率递减、横坐标递增,用单调队列维护。(为什么这里单调性相反,而单调队列没有退化为单调栈?因为上凸壳和下凸壳的性质是反的,画图理解很明显。)

  • 【任务安排 3 的变式一】:假如 \(t_i\) 有可能是负数呢?

    此时斜率不满足单调性了,而横坐标依旧满足。我们可以使用单调队列维护整个凸壳,每次在凸壳中二分查找斜率即可。(当然,这道题中由于没有额外决策下界限制,单调队列退化为了单调栈。)

  • 【任务安排 3 的变式二】:假如 \(c_i\) 有可能是负数呢?

    蓝书上给的解决方案相当巧妙——设计一个反向 DP 方程,使得 \(t\) 为横坐标 \(c\) 为斜率,即可用上述方法求解。

  • 【任务安排 3 的变式三】:假如 \(c_i, t_i\) 都有可能是负数呢?

    横坐标、斜率都不单调。这时我们就需要一个支持随意插入的数据结构——平衡树,来维护下凸壳了。(只能维护不带删除点操作的下凸壳,详见 动态凸包模板题。)



九、决策单调性与四边形不等式

1. 基本概念

  • 决策单调性:对于一维线性 DP 转移方程 \(f[i] = \min_{0 \le j < i} f[j]+val(i, j)\) 的任意 \(i_1 < i_2\),对于二者的决策点必然成立 \(j_1 \le j_2\)。这是一类 DP 优化的突破口。

    【注意:决策单调性并不意味着在 \(i\) 固定时,对于 \(j_1 < j_2\) 一定有 \(f[j_1]+val(i, j_1) < f[j_2]+w(i, j_2)\)。】

  • 四边形不等式:若对于任意的 \(a \le b \le c \le d\),都有 \(w(a, d)+w(b, c) \ge w(a, c)+w(b, d)\),则称函数 \(w\) 满足四边形不等式。

    这玩意儿为什么被称为“四边形”不等式呢?因为它跟四边形“对角线之和大于对边之和”的性质刚好相反——“包含”大于“交叉”。

    image

    四边形不等式一般被用于证明一些 DP 方程的决策单调性。可以证明,在状态转移方程 \(f[i] = \min_{0 \le j < i} f[j]+val(i, j)\) 中,如果 \(val\) 满足四边形不等式,则 \(f\) 具有决策单调性。具体证明见 OIwiki,懒得证。

  • 二维区间 DP 的决策单调性:对于转移方程 \(f[i][j] = \min_{i \le k < j}f[i][k]+f[k+1][j]+w(i, j)\),令 \(p[i][j]\) 表示其决策点。若满足 \(p[i][j-1] \le p[i][j] \le p[i+1][j]\) 则称其满足决策单调性。

  • 区间包含单调性:若对于任意的 \(a \le b \le c \le d\),都有 \(w(b, c) \le w(a, d)\),则称函数 \(w\) 满足四边形不等式。

    通过证明,我们有结论:

    • \(w\) 满足四边形不等式区间包含单调性,则 \(f\) 满足四边形不等式。

    • \(f\) 满足四边形不等式,则其满足决策单调性。


2. 一些题目的分析技巧

  • 四边形不等式的另一种定义:若对于任意 \(a < b\),都有 \(w(a+1, b)+w(a, b+1) \ge w(a, b)+w(a+1, b+1)\),则可以证明 \(w\) 满足四边形不等式。

    点击查看证明

    使用数学归纳法。

    假设 \(w(a, c+1) + w(a+k, c) \ge w(a, c)+w(a+k, c)\) 成立。

    \(a+k < c\) 代入原式得:\(w(a+k+1, c)+w(a+k, c+1) \ge w(a+k,c)+w(a+k+1, c+1)\)

    将两式相加得:\(w(a, c+1) + w(a+k+1, c) \ge w(a, c)+w(a+k+1, c)\)

    故此,对于 \(a \le b < c\),有:\(w(a, c+1)+w(b, c) \ge w(a, c)+w(b, c+1)\) 成立。再代入一下 \(b=c\),发现也成立。
    此时再仿照上述步骤即可拓展到一般四边形不等式 \(a \le b \le c \le d\) 的形式。

    这一条是最常用的。

  • 满足四边形不等式的四个函数类

    概述一下:

    • 满足四边形不等式的函数经过线性变换依旧满足四边形不等式。

    • \(w\) 可拆解为两个函数之差,则其满足四边形不等式。

    • 若函数 \(h(x)\) 是一个单增的下凸函数,若函数 \(w\) 满足四边形不等式,则复合函数 \(h(w(x, y))\) 也满足四边形不等式。【例:\(h(x) = \sqrt x\)

  • 打表

    在网上看见这么一种说法:“四边形不等式证明可能比较困难,做题时一般都是看到刚好要将复杂度削掉一个 \(n\),立马打表验证一下,然后直接冲。”


3. 实现

对于一维的情况,决策单调性有两种主要实现方法,均可以达到 \(O(n \log n)\) 复杂度。

  • 分治法

    这种方法的思路十分简洁:对于当前区间 \([l, r]\),如果已经找到了其决策所在区间 \([lk, rk]\),即可先 \(O(n)\) 算出 \(mid = \frac{l+r}{2}\) 的决策位置 \(k\)。此时,我们就知道 \([l, mid-1]\) 的决策所在区间为 \([lk, k]\)\([mid+1, r]\) 的决策所在区间为 \([k, rk]\),再继续递归求解即可。

    inline int W(int x, int y);
    
    inline void Get(int x, int lk, int rk){//找决策位置
    	for(int i = lk; i <= min(x-1, rk); i++)
    		if(W(x, i) > W(x, p[x]))	p[x] = i;
    	return;
    }
    
    inline void Div(int l, int r, int lk, int rk){//分治
    	if(l > r)	return;
    	int mid = (l+r)>>1;
    	Get(mid, lk, rk);
    	Div(l, mid-1, lk, p[mid]);
    	Div(mid+1, r, p[mid], rk);
    	return;
    }
    
    //主函数初始化
    Get(n, 1, n); Div(1, n, 1, p[n]);
    

    但是这种方法有一定的局限性:它只能用于求 \(f[x] = \min_{0 \le i < x} val(i, x)\),而无法直接应用于带有 \(f[i]\) 线性 DP 转移方程。因为它的复杂度正确性来源于 \([lk, rk]\) 的长度始终不大于 \([l, r]\),使得我们能够对 \(mid\) 的决策进行 \(O(n)\) 的暴力更新。这也就要求我们要在递归的开头必须求出正确的 \(rk\)。而对于线性 DP,想必就无法做到在递归一开始就计算出 \(f[n]\)

  • 二分 + 单调队列法:直接看 OIwiki 吧,说得很清楚。

对于二维区间 DP,我们一般采用以区间长度为阶段的转移方式,暴力枚举决策点即可。


4. 题目



十、矩阵 & 倍增优化 DP

1. 基本概念

我在 线性代数学习笔记 中写了一些关于矩阵的基本概念,并总结了矩阵乘法的三大用途:

  • 运用矩阵快速幂实现对同一递推式的加速递推

  • 套用数据结构实现任意区间查询矩阵乘法。

  • 套用数据结构动态维护矩阵乘法。

由于这一节的标题为“DP 优化”,因此这一节主要体现矩阵乘法的第一条用途。第二、三条用途在下一节“动态 DP”中体现地更为集中。


2. 矩阵优化

怎么用矩阵优化?将 DP 方程写成矩阵的形式,因为矩阵乘法有结合律,对它做矩阵快速幂即可。计数类 DP、最优化类 DP,皆可以。

什么时候能用矩阵优化?当状态数很小,但阶段很多,转移方程又没有变化的时候,而且有“恰好 \(k\) 个阶段”之类的字眼时,就可以考虑。一类矩阵优化的经典题型为恰好 \(k\) 步路径问题,以下大多数例题出自该题型。


3. 倍增优化

其实倍增优化本质上和矩阵优化是一样的。只是如果每次计算一个规模为 \(n \times n\) 规模的矩阵乘法,都有一个 \(O(n^3)\) 的常数。故此,当信息的转移足够简单时,我们就将矩阵结构略去,使用倍增优化。

当然,一边变简单了,另一边就会变困难:需要用到倍增的题目相较矩阵就不会那么明显。提示我们这道题要使用倍增的有这么几点:“固定转移”“多次询问”“恰好”。而“固定转移”这条性质的发现,往往需要用到一点贪心思维。

倍增优化的步骤大致就是:预处理 + 拼凑查询



十一、动态 DP

1. 基本概念

“动态 DP”是一类将简单的树形 DP 问题,如最大权独立集,加上了动态的点权修改操作或多次询问操作,从而将码量增大了 INF 倍的神奇题目。

算法简单概括就是:矩阵乘法 + 树链剖分

在修改一个点的点权后,只有该点到根节点路径上点的 DP 值发生了改变。故考虑树链剖分进行维护。

为了用数据结构动态维护,要设法将 DP 方程套入矩阵乘法中。为了方便矩阵维护,我们将 DP 值拆为“轻儿子”部分和“重儿子”部分。对于一个点,我们只储存其轻儿子部分;重儿子部分每次查询都需要通过计算对应重链上的矩阵乘法来获得。


2. 实现注意事项

  • 单位矩阵:主对角线为 INF,其余位置为 0 的矩阵。在 Query 时用到。

  • 注意设计从矩阵中取出答案的向量。


3. 例题

posted @ 2024-01-07 14:19  David_Mercury  阅读(17)  评论(0编辑  收藏  举报