ZROI 学习笔记之动态规划

都别催!!!等我有时间了例题和详细讲解都会补回来的!!!

CHANGE LOG

  • 2023.10.11 补充细化 7 月 26 日单调队列 / 斜率优化 DP 部分。

7.24 区间 DP & 树形 DP

1. 区间 DP

  • 合并:即将两个或多个部分进行整合,当然也可以反过来;
  • 特征:能将问题分解为能两两合并的形式;
  • 求解:对整个问题设最优值,枚举合并点,将问题分解为左右两个部分,最后合并两个部分的最优值得到原问题的最优值。

2. 树形 DP

  • 经典问题:树的直径
  • 换根 DP:《取消父子关系,再给人家当儿子》

wcy 硬垒

  • 算法介绍:wcy 硬垒

  • 命名原因:wcy 把扫描线合理外推,运用到甚至可能不算扫描线的方面,并称其为“扫描线的思想”,惨遭某人批评,于是,wcy 硬垒应运而生。

  • 算法内容:类似扫描线的思想,将点集(区间端点集)按照某个坐标排序后分层,按层次从低到高逐层解决或层层合并,转换为一个或多个单层问题,再进行 DP 或查询操作。

  • 例题:CF1372E,暑假模拟赛 D1T2。

    (虽然这两道题 wcy 自己都没有把正解完全想出来,但由于他的想法与题解十分接近,他固执的称这就是所谓的 wcy 硬垒。)


7.25 状压 DP & 数位 DP

1. 状压 DP

状压 DP 是动态规划的一种,通过将状态压缩为整数来达到优化转移的目的。

—— OI WIKI

2. 数位 DP

3. Ex:线性基


7.26 DP 优化

1. 前置知识

1.1 单调队列

笔者觉得这个东西本质还是单调栈,所以我们从单调栈讲起。

  • 什么是单调栈?

    单调栈用于解决 NGE(Next Greater Element,下一个更大元素) 问题。

  • 如何实现单调栈?

    首先其必须是一个栈。考虑在每次入栈时将当前元素与栈顶元素比较,如果当前元素更小就老老实实入栈;但如果更大,那么该元素就应该是栈顶元素的 NGE,然后将栈顶元素弹出,直至当前元素比栈顶元素小。

    这个做法应该不难理解,就是保持栈中的单调性。在我们举的例子中,就是维持栈中元素从底到顶单调不增。接下来,我们回到正题。

  • 什么是单调队列?

    单调队列用于解决 滑动窗口 问题,一般是 一个序列的每个定长连续子序列的最大 / 最小元素

  • 如何实现 / 其和单调栈的异同?

    注意到单调栈中维护的其实就是 从最开始到当前位置的末项为当前位置的单调序列,比如解决下一个更小元素(就是刚才单调栈的例子反过来)的单调栈,其栈底的元素必然是目前为止最小的元素。

    我们需要的单调队列,就是需要这么一个单调序列,只不过其栈底元素应该是 从子序列起点起的最小元素。那么,这和单调栈有什么差异的?

    差一个 时效性,即栈底元素可能已经 不在要求的子序列中 了,这时我们就应该 弹出栈顶元素。发现什么了吗?

    这是一个 双端队列。所以,我们只需要:

    1. 将栈顶作为队尾,将栈底作为队首;
    2. 插入元素时,将队尾劣于当前元素的元素弹出,再插入当前元素;
    3. 及时将队首失去时效性的元素弹出;
    4. 队首元素即是滑动窗口答案。

    可以感性的理解为,队尾在存储后备人才,队首是先锋。而如果你作为后备人才时,比人家年纪大(位置靠前),还比人家菜(更劣),那你就该 pop 了,把人家送进去吧;如果你虽然年纪大,但是比人家牛,那就让人家做你的替补,你的先锋全部退休之时,就是你成为先锋的那一天。

滑动窗口作为一类优化的模型,经常被广泛套用,故掌握单调队列极其重要。

1.2 DP 规模的描述

我们通常用 \(t\)D / \(e\)D 来描述动态规划 规模 的类型,其中 \(t\) 表示问题大小,\(e\) 表示转移时依赖子问题的大小。即状态数 \(n^t\),每个状态依赖于 \(n^e\) 个前驱状态的信息。除非问题有特殊性质,解决 \(t\)D / \(e\)D 动态规划需要 \(O(n^{t+e})\) 的时间复杂度。

下文中可能会用这种描述来定义优化的常见应用场景。这里也同时给出一些常见的规模:

  • \(1\)D / \(1\)D:最长上升子序列。
  • \(2\)D / \(0\)D:最长公共子序列,普通背包问题。
  • \(2\)D / \(1\)D:多源最短路 Floyd。

2. 基于决策单调性的优化

2.1 大前提:决策单调性

  • 如何理解决策单调性?

    通常情况下,决策单调性 体现在 1D 维度 上。形式化定义是,设 \(f\) 表示当前层,\(g\) 表示下一层,设 \(g_i\)\(f_{p_i}\) 处转移且 \(p_i\) 最小 / 最大,则 \(i<j \implies p_i \leq p_j\)

    形象的理解,如果我们把每一阶段的状态与上一阶段决策这一状态的状态做连边(即只取状态转移时最优的状态转移画出边来),形成一个分层图,那么这里面每一条路径必然 不交

  • 判断:四边形不等式

    四边形不等式:\(a<b<c<d \implies w(a,d)+w(b,c) \text{ worse than } w(a,c)+w(b,d)\),即 包含劣于相交

    \(f_j\) 可以从 \(f_i+w(i,j)\) 转移到得,如果转移中的贡献函数 \(w(u,v)\) 满足四边形不等式,那么 \(f\) 具有决策单调性。

    利用反证法即可证明这一结论,此处省略。同时,决策单调性形象的理解也让我们很容易理解这一结论的正确性——因为单调性决策的最优转移路径 不交

2.2 单峰性 - 单调队列优化

单峰性:对于某个状态的最优转移点,其左边的贡献向左单调递减,右边也向右单调递减。(一个单峰函数)

对于这样的状态,我们直接施展单调队列维护就可以了。\(O(n^2)\) 的转移可以降到 \(O(n)\)

最经典的一类问题是,如果转移方程可以写成 \(f_i = \min { f_j + cost_{i,j} }\),且可以证明(到考场上可以换成猜想)这个问题具有决策单调性,(表现为若 \(f_i\) 转移自 \(f_j\),则对于任意 \(k>i\) 如果从 \([1,i-1]\) 中转移则 \(f_j\) 仍然最优。)那么一般就可以直接使用单调队列维护了。

2.3 分治法

  • 常见于 \(2\)D/\(1\)D 的 DP。
  • 对于每个区间,暴力寻找中点的决策点,然后递归寻找两边,由于决策单调性,中点左边的点决策点也必然在中点决策点左边。
  • 时间复杂度 \(O(n \log n)\)

2.4 斜率优化

常见于 \(1\)D/\(1\)D 的 DP。

一般来说是考虑能否将转移方程写成

\[f_i = f_j + cost_i + cost_j + F_i F_j, \]

即一些只和 \(i,j\) 中一个有关的项和与两者都有关的项。然后考虑写成

\[f_j + cost_j = - F_i F_j + f_i - cost_i \]

的形式。我们将他对比一次函数截距式:

\[y = kx + b, \]

便得出一个 \(y = f_j + cost_j, k = -F_i, x= F_j, b= f_i - cost_i\)一次函数

有什么用呢?

我们来看看这个一次函数有什么意义。

  • 首先,对于每一个要求的状态 \(i\),我们要求的值 \(f_i = b + cost_i\),所以我们的目的就是 最大 / 小化截距。(最大还是最小看转移方程取 \(\min / \max\)。)
  • 注意到斜率 \(k\) 只与 \(i\) 相关,而 \(i\) 已确定,所以 斜率 是定值。
  • 最后,注意到这个空间中的每一个点对 \((x,y)\) 实际上是 \((F_j , f_j + cost_j)\),也就是说每一个点对应一个状态 \(j\)
  • 这样,转移就变成了 最优化定斜率过某一定点的直线的纵截距。最优化指最大 / 小化中的满足题意的一个。

这个问题如何解决?考虑哪个点会成为最优化截距时直线经过的点。对于每一个斜率 \(k\),这些有可能被选中的点就形成了一个凸包。于是我们 维护凸包 即可。

一般来说,我们解决的问题斜率是 单调增长 的,且插入的点的 \(x\) 坐标有序,即对于 \(i \ in [1,n]\)\(F_i\) 单调,且转移过程是 \(1 \to n\) 遍历,那么我们就可以直接使用 单调队列 维护;如果查询的斜率不单调,但 \(x\) 坐标单调,则可以使用单调栈维护凸包,在上面二分寻找查询斜率的直线与凸包的切点;如果没一个单调的,那就需要使用李超线段树或动态凸包维护了。

2.5 二分队列

  • 可以解决状态转移需要同阶段其他状态的情况。(比如 \(dp(i,j)\) 需要由 \(dp(i,k)\) 推出。)
  • 要求转移时的贡献能快速计算,一般是 \(O(1)\)
  • 设当前要求 \(dp_i\),考虑建立存储三元组 \((j,l,r)\) 的队列,表示 \(l \sim r\) 的最优决策点是 \(j\)
  • 每次判断队首三元组是否过时,若 \(r<i\) 则弹出队首。否则将 \(l\) 赋值为 \(i\)
  • 不想贺魏老师的博客了……有什么独到的理解了我再写。

3. 常见背包优化

3.1 缺一分治

  • 优化背包 DP
  • 每次向下递推时对另一个区间背包(所以要求你可撤销),比如处理 \([l,mid)\) 的时候把 \([mid,r)\) 背包了,这样等到了边界条件的时候,你就获得了一个必然不选 \(l\),对其他物品进行背包的 DP 结果了。

3.2 倍增背包

  • 或者我们称为 二进制拆分 + 多重背包
  • 将一个物品的数量 \(w\) 转化为 \(2\) 的幂次,注意 不是 \(w\)二进制表示!而是 \(2^0,2^1,2^2,\dots,2^k,w-\sum \limits_{i=0}^{k}2^i\),用这种方法拆分,就可以在 \(\log w\) 内用这几项的所有组合表达 \(1 \sim w\) 的所有选择了。

4. 数据结构优化

  • 线段树
  • 倍增
  • 并查集

5. 期望 DP 的优化

  • 观察性质
  • doubleint

7.27 矩阵乘法及其相关

1. 前置知识

1.1 矩阵乘法

  • 具有结合律,但不具有交换律。(故可以使用快速幂)

  • 基础运用:优化线性递推与线性 DP。(详见山东游记)

  • 广义矩阵乘法:

    \[C_{i,j} = \bigoplus \limits_{k=1}^n A_{i,k} \otimes B_{k,j} \]

    只要 \(\bigotimes\) 满足 结合律,且 \(\bigotimes\)\(\bigoplus\) 满足 分配律,那么其就满足 结合律。常见的广义矩阵乘法比如:

    • \(\max,+\) 卷积,比如求树的最大权独立集,即 DDP 的模板题。
    • \(\operatorname{or},\operatorname{and}\) 卷积,图论的邻接矩阵在此广义矩阵乘法下的 \(k\) 次方描述了点 \(i\) 出发走 恰好 \(k\) 步可以到达的节点。

1.2 倍增思想

再重要不过了。用过倍增思想我们可以轻松的在 \(\log\) 的复杂度内求解幂次:

template <typename _Tp> _Tp ksm(_Tp base,int ind,int mod=INT_MAX) {
	_Tp ans=1;
	while(ind) {
		if(ind&1) ans=ans*base%mod;
		base=base*base%mod; ind>>=1;
	}
	return ans%mod;
}

亦或者快速求解 \(\sum \limits_{i=1}^{n-1} a^i\):(详见山东游记)

template <typename _Tp> _Tp fac_pow(_Tp base,int ind,int mod=INT_MAX) {
	_Tp ans=0,now=1; int st[100] {0};
	while(ind) {st[++st[0]]=ind&1,ind>>=1;}
	while(st[0]) {
		ans=(ans+(now*ans%mod))%mod,now=now*now%mod;
		if(st[st[0]]) ans=(ans+now)%mod,now=(now*base)%mod;
		st[0]--;
	}
	return ans;
}

熟练的利用倍增,和矩阵搭配会更香哦。

2. 动态 DP

一般指树上的动态 DP。

动态 DP 主要用于解决一类问题。这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作。

—— Tweetuzki

  • 写出转移方程,使用矩阵转移,但 \(n^3\) 的转移有点承受不了。
  • 注意到 DS 可以快速转移状态,而树剖具有最大链深度 \(O(\log n)\) 的优点,将二者融会贯通。
  • 黄色的树林中分出两条路:线段树与全局平衡二叉树。

3. Ex:多项式取模

关于特征多项式,笔者也不会 qwq,别指望笔者能写出什么,笔者只能写自己会的。

反正我们知道,对于一个函数,给定其递推式,如

\[f_i=f_{i-1}+f_{i-2}, \]

那么移项之后,

\[f_i-f_{i-1}-f_{i-2}, \]

这就是这个函数的特征多项式。其实准确来说,移项之后是

\[f_i-f_{i-1}-f_{i-2}=0, \]

所以可以浅显的理解为,多项式对该多项式取模就相当于对 \(0\) 取模,结果不变,只是次数降到了特征多项式次数以下。(?我知道这样很不严谨,但我学艺不精 qwq,大家领会精神吧……)

所以,我们在每次乘法后对特征多项式取模,就可以把次数压住啦。举个例子,一个函数的递推式是 \(f_i=\sum \limits_{j=1}^{1000} f_{i-j}\),用 \(n^3\) 的矩阵乘法递推肯定要寄,这时候就可以令 \(x^i = f_i,x_i=\sum \limits_{j=1}^{1000} x^{i-j}\),搭配快速幂,直接用多项式乘法正常 \(n^2\) 卷就可以啦~

那么,如何取模呢?

考虑到取模其实本质上是一直减,我们考虑不断将原多项式减去特征多项式的倍数即可。从贪心的角度,我们考虑每次消掉最高次项。这里举个例子,\(x^5\)\(x^2-x-x^0\) 取模:

\[\begin{aligned} x^5 & = x^5 - x^3 ( x^2 - x - x^0 ) \\ & = x^4 + x^3 \\ & = x^4 + x^3 - x^2 ( x^2 - x - x^0 )\\ & = 2 x^3 + x^2 \\ & = 2 x^3 + x^2 - 2x ( x^2 - x - x^0 ) \\ & = 3 x^2 + 2x \\ & = 3 x^2 + 2x - 3 ( x^2 - x - x^0 )\\ & = 5x + 3x^0. \end{aligned}\]

\(x^0\) 可不一定是 \(1\) 哦,比如上面的 肥不拉几 斐波那契数列中 \(x^0=f_0=0\),斐波那契数列的特征多项式就是 \(x^2-x-x^0\),那么根据上面的取模就可以知道 \(fib_5=5fib_1+3fib_0=5\) 了。


7.28 欢乐 ACM

太欢乐了。

—— 总结一下上午

—— 我 D 后缀数组超纲了就会一半

—— agy 分治没治出来

—— zzm 数位 DP 假了

—— 痛失三道题……

made,怎么什么都不会……

本来想写解题报告来着,被恶心到了,鸽了吧。

posted @ 2023-07-24 22:24  二两碘酊  阅读(86)  评论(0编辑  收藏  举报