%zxy
zxy的思维技巧
(不要脸地开贺)
1 dp
1.1 常规 dp 的思维过程
1.1.1 问题转化
- \(\color{red}\bigcirc\) 比如你要让所有点被覆盖,那么状态可以设计成覆盖一段前缀,并且中间不允许出现断点:CF1476F Lanterns / Tutorial
- 题目特点:要求所有点都被覆盖,每个覆盖物是一段区间
- 思路:设计状态 \(f_i\) 表示覆盖一段前缀的最小代价
- 适应本题:改记 \(f_i\) 表示考虑到第 \(i\) 个点,能连续覆盖的最长前缀
- \(\color{green}\surd\) 序列上的路径问题,可以转化成起点和终点的匹配问题,dp 匹配的权值,记录匹配的标记就可做:HDU6157 The Karting / Tutorial
- 题目特点:序列上连续走动的路径/环问题
- 思路:常见的线头 dp,\(f_{i,j,k}\) 表示当前考虑到第 \(i\) 个位置,当前剩下 \(2j\) 个向右的线头,已经钦定了 \(k\) 个拐点
- 另解:从只考虑关键点的角度出发,可以把同向走的每一段拆成两个前缀和之差,将贡献全部放在关键点上计算
- \(\color{red}\bigcirc\) 多过程的题,不妨考虑末状态具有什么性质,直接对末状态进行计算。比如一类期望题中,某一种方案的定义方式最后导出等概率出现,就可以直接对此方案计数了:Loj6406 绿宝石之岛 / Tutorial
- 题目特点:问题问的是一个过程,而这个过程相当繁琐,直接针对过程难以 dp
- 思路:考虑越过这一繁琐的过程,直接考虑末状态的特征,可能能发现一些可以入手的特点,例如本题的关键就在于通过计算发现每个末状态出现概率相同
- 题目特点 2:询问的是一个序列前 \(k\) 大的数的信息
- \(\color{blue}\dagger\) 有用的状态设计技巧:考虑改为从大到小扫值域轴,设计状态为 \(f_{i}\),表示当前已考虑 \(\ge\) 某个阈值的所有数(这个数甚至可以不记录在状态里面!这是很重要的一点),这样的数一共有 \(i\) 个,信息和。转移的时候,我们往 \(j\) 个数的后面加个 \(1\)(\(j\ge i\))。这样的操作使得前 \(k\) 大出现的时间能被清晰地呈现(就是从 \(i<k\) 转移到 \(j\ge k\) 的时候),同时可以发现它能够有效地不重不漏地计算所有情况。注意转移时需要乘上组合数 \(\binom{j}{i}\),并且注意存在同层转移。
- \(\color{red}\bigcirc\) 高维的问题,可以通过技巧拆分成不相关的低维问题,比如 \(45^\circ\) 旋转:Jumping sequence / Tutorial
- 题目特点:在二维平面上的行走过程,每步形如上下左右
- 思路:遇到二维平面上的问题,首先应当思考是否能够用某种方式将两维独立开来,从而降维为一维问题
- \(\color{blue}\dagger\) 降维技巧:将坐标系旋转 \(45^\circ\),两维就独立了!
- \(\color{orange}\Delta\) 经典问题:给定 \(n\) 个物品,每个物品体积至多为 \(V\),要求选出若干物品使得其体积和尽量接近 \(W\)(其中 \(W\) 是 \(O(nV)\) 级别的),时间复杂度 \(O(nV)\)。
- \(\color{blue}\dagger\) 经典问题的技巧:大致思路:先贪心选,选到恰好 \(<W\) 为止,可以证明最优解一定可以从此在 \([W-V,W+V]\) 之间波动得到,定义 \(\mu(l,r,w)\) 为是否存在一种方案用上 \([1,l]\) 中的所有数和 \((l,r]\) 中的若干个数,使其和为 \(w\)。定义 \(f(r,w)\) 为使得 \(\mu(l,r,w)\) 为真的最大的 \(l\)(不存在为 \(-\infty\))。转移形如 \(f(r-1,w)\rightarrow f(r,w),f(r-1,w)\rightarrow f(r,w+a_r),[l\le f(r,w)]l\rightarrow f(r,w-a_l)\)。直接做是 \(O(n^2V)\) 的,但是发现在第三种转移中如果 \(l\le f(r-1,w)\) 的话这一转移可以被 \(r-1\) 所覆盖,于是只用枚举 \((f(r-1,w),f(r,w)]\) 区间内的 \(l\),总时间复杂度为 \(\sum O(f(r,w)-f(r-1,w))=O(nV)\)。
- \(\color{green}\surd\) 尽可能的简化问题也是问题转化的一部分,比如把具有平行关系的点缩在一起:Black, White and Grey Tree / Tutorial(题解有误,无需 dp,且 dp 意义是错的)
- 题目特点:询问进行一些较复杂的操作,达到某一最终状态,所需要的最小操作次数
- 思路:由于操作过于复杂,直接 dp 不可行,因此通过推性质简化问题、规约问题,直至简化到适合 dp 的形式。例如本题的关键即为推得最长黑白交错段性质
- 注意:最终无需 dp,经过思考可以发现所有灰点可以省去。dp 的意义并不正确,因为题解中的 \(f(u,i)\) 和 \(g(u,i)\) 的最优方案并不能同时取到。
- \(\color{red}\bigcirc\) 计数问题中任何性限制原则上优于存在性限制,可以通过切换限制主体来完成转化:Reunion / Tutorial
- 题目特点:(经过拆贡献的转化后)问的是所有使得“存在一个点满足某一条件”的染色方案
- 思路:以上特点意味着算重是一个巨大的问题,所以我们优先考虑将“存在性问题”转化成“任意性问题”再考虑如何 dp。本题中即为“存在一个点,其周围 \(r\) 个点中没有黑点”转化成“对于所有黑点,它们周围 \(r\) 个点的并集不为全集”。这件事情的本质是将考虑的对象改为其对称的对象,从而完成“存在”到“任意”的转化。
- \(\color{blue}\dagger\) 状态设计技巧 —— 用决策覆盖压缩状态:直观地将所有有关信息都列出来会得到 \(f(u,i,j)\) 表示 \(u\) 子树内最深未覆盖的点为 \(i\),\(u\) 子树内出去覆盖的最大距离为 \(j\),方案数。但是经过思考,我们会发现,如果子树内还有未被覆盖的点,那么覆盖这个点的圆一定会偏序掉这个子树出去的半径 \(j\),所以我们只需要关心 \(i\) 即可。于是这两种状态被压成一种,如果还有未被覆盖的点则记录 \(i\),否则记录 \(j\),这样状态就是两维的了。树形背包知对单个 \(r\) 的复杂度就降到了 \(O(n^2)\)。
- \(\color{red}\bigcirc\) 子序列问题可以通过证明不交转化成区间问题,要向简单的 dp 模型靠拢:AmShZ Wins a Bet / Tutorial
- 题目特点:不断进行若干次复杂的操作,求最终能达到的最优状态
- 思路:首先第一步也是通过决策覆盖简化操作,发现每次删的一定是相邻一对括号,于是合起来就是若干段连续的合法括号区间,这就形如子序列 dp。
- 题目特点 2:子序列 dp
- 应对方式:子序列 dp 的要素在于选择接下来是走一步还是向右跳过一段,特别是当子序列的性质只和其相邻两个元素紧密相关的时候。
- 题目特点 3:字典序 dp
- \(\color{blue}\dagger\) 字典序 dp 的解决技巧:首先根据字典序的定义,比较时会优先比较靠前的字符,所以 dp 应当采取从后向前的顺序。另一个技巧是用 Trie 树来解决两个 dp 值比较以及赋新值的问题,从一个状态转移过来对应于在某个节点上新挂一个叶子,两个 dp 值的比较可以用倍增+哈希解决,而每新增一个叶子时可以动态维护倍增数组。
1.1.2 发掘性质
理清思路真的很重要,拿到题你可以先想想什么东西在什么情况下合法。
- \(\color{red}\bigcirc\) 什么时候需要你去发掘性质?当你发现直接 dp 需要考虑的情况太多了,你可以手玩找一些最优解需要满足的必要条件,才能让你的 dp 有的放矢,例题:CF1368H1 / Tutorial、Division into Two(做过了,不做了)
- 题目特点:题中给定的关键点被分为两个集合,需要在两个集合的点之间连若干条边,求最大边数
- 思路:这“显然”是一个网络流模型,两个集合其一为 \(S\) 另一个为 \(T\),在每两个网格点之间建无向边跑最大流即可。然后典型的最大流转成最小割算,问题就变成了:对每个格点染色,最小化两端异色的线段条数。
- 转化后问题的特点:这个问题相较于原问题显得更加传统,也显得更加可做,这是一个进步。但是直接 dp 面临的问题是:这是一个二维平面上的 dp,能够产生后效性的元素很多(有整整一行),所以直接下手只能进行插头 dp 之类的指数级做法。
- 思路 2:为了能够化简问题,剪除一部分后效性,我们需要去发掘性质。发掘性质的结果是发现同一行(或列)必须颜色全部相同,于是其中一维就被压下去了,剩下的 dp 就是显然的了。
- Note:这道题自己思考的时候始终抓住了“二维平面”这一点去思考,尝试分离两维,以及推出了“每条边必定互不交叉”之类的性质,但是从最终的解法来看,这些思考其实和正解相差甚远。感觉这也说明必须时刻记得跳出思考的死局,去想想别的思路。
- Note 2:网络流题貌似都不会存在别的做法,并且别的做法都难以逼近网络流的思路(除了少见的反悔贪心),所以遇到做不动的题还是要先考虑考虑网络流有没有可能。
- \(\color{green}\surd\) 性质是针对限制而来的,在限制较少的题目中可以去往考虑更少情况的方向猜性质:CF573D / Tutorial
- 题目特点:和经典问题很像,如果去掉“不能骑同一匹马”的限制就是典中典排序不等式
- 思路:既然和经典问题很像,就要类比经典问题的做法(有时候这样做会错,但是试一试总是不亏的)。感觉一下会发现选很远的马可以被轻易交换掉,于是就可以注意到只会交换很近的马。优化 dp 是很简单的事情。
- \(\color{green}\surd\) 在具有强烈过程性的题目可以往结果的方向猜性质:To Make 1 / Tutorial、模拟赛2-A(无题源)、Eternal Average
- To Make 1:
- 题目特点:和前面的《绿宝石之岛》很像,也是问题具有强烈的过程性,因此可以直接考虑最终结果的特征,什么样的最终结果可以被达到。
- 思路:考虑每个点被除了几次 \(k\),发现一定有一种操作顺序使得它就是除了这么多次 \(k\),于是对着这个 dp 即可。
- 注意点:别忘了 bitset 优化 dp。是常用的。
- \(\color{red}\bigcirc\) 很多时候讨论特殊情况可以得到很好的性质:泳池 / Tutorial(讨论 0 的情况可以得到调和级数的性质)
- 题目特点:1. \(n\) 的大小很大,但是 \(K\) 的大小比较小 2. 限制形如“任意一个区间 \(\le x\)”
- 思路:1. 先预处理出来空闲长度为 \(K\) 以内的 dp 值,而对于较大的 \(n\),可以把 dp 转成线性递推,用快速幂加速 2. 枚举最小值的位置,转移到子问题
- \(\color{green}\surd\) 如果限制的主体数量级巨大(比如集合、子序列),那么可以考虑归纳、递归的方法描述限制。并且使用这种方法还有一种好处,就是递归子问题很容易拓展到 dp 的形式:Density of subarrays / Tutorial
- 题目特点:需要判断有几个子序列满足某个性质,而这个性质相对较奇怪
- 思路:考虑怎么刻画这个性质,或者说怎么判定这个性质。最终选择的刻画方式是从左往右扫,当所有数都出现过一遍的时候 \(p\leftarrow p+1\) 并将 vis 清零。但是我们要记录“是否每个数都出现过一遍”,这个没法 dp,所以我们改为“找到下一个最近的数,使它前面所有数都出现过”,这样就可以预处理出 \(g_{l,r}\) 然后 dp 了,时间复杂度 \(O(n^3)\)。平衡复杂度的部分不是重点。
- \(\color{blue}\dagger\) dp 数满足某性质的子序列/集合技巧:什么样的描述方式方便 dp?如果能花 \(O(1)\) 的额外空间解决判定问题,那么这就可以 dp;如果需要维护一个 \(O(n)\) 空间的数据结构维护,则不可以 dp;如果需要 \(O(n)\) 空间,但是判定问题可以用类似 dp 的方式解决,则可以 dp;如果可以判定方式可以递归到方向合适的子问题进行,则可以 dp。
1.1.3 思考决策状态
- \(\color{red}\bigcirc\) 可以先使用枚举法帮助思考我们需要决策的状态,然后用 dp 加速枚举的过程:Sorting Books / Tutorial;Insertion Sort / Tutorial
- 题目特点:需要进行若干次操作,每次操作需要把一个数放到另一个位置,求最少的操作次数
- 思路:从“考虑末状态”的观点出发,考虑最终哪些数被操作,从而把数分成“不变量”和“变量”两种类型,那“不变量”由于它们是不变的,所以他们在数列、值域上的顺序都和原来相同,这就有利于 dp;至于那些“变量”的变化的处理,我们可以考虑在不变量的基础上,用一些简单的方法得知变量的变化规律,从而达到将“所有数的变化问题”转变成“不变量的钦定问题”上面,这就有利于 dp 了。
- \(\color{red}\bigcirc\) 计数题可以想一想需要知道什么量可以用计数原理快速算方案,然后我们用 dp 决策这些量即可:一拳超人 / Tutorial
- 题目特点:需要对于每种方案的计数结果进行求和,也即我们需要进行两重计数,外层是用 dp “枚举”的,内层是需要用计数原理进行计算的(即组合数,容斥,乘法原理之类的)
- 思路:我们发现后面半部分其实是不好乱动的,毕竟计数原理中的一些东西不是可以修改的,一个经典的例子就是“给定 \(a_i\),数 \(\forall i,p_i\le a_i\) 的排列 \(p_i\) 个数”,这个我们必须要按 \(a\) 中值域的顺序扫,而不能按下标顺序扫之类的。对于本题来说也是一样。因此,对于这种题目,扫值域进行 dp 是一个极其重要的技巧。而在这题里面,对于另一维的限制(最长上升子序列为 \(k\)),扫值域和扫下标没太大区别,所以他是可以将就的,于是我们就可以确定这题应该按值域顺序去扫了。
- \(\color{blue}\dagger\) 最长上升子序列的 dp 技巧:若我们要计算最长上升子序列为 \(k\) 的序列个数,考虑我们计算最长上升子序列的算法,过程中我们会维护一个数组 \(val_i\) 表示最长上升子序列的长度为 \(i\) 时最后一个元素的最小值,然后每次扫到下一个元素 \(a_i\) 时,找到序列中第一个 \(\ge a_i\) 的位置将他改为 \(a_i\)。而这个过程是可以压位的!记录一个 \(V\) 位二进制数,然后这个过程就等价于每次找出第 \(i\) 个位置后面的第一个 \(1\) 把它挪到这里(如果不存在就新建一个),于是就可以 \(2^V\) dp 啦!
- \(\color{red}\bigcirc\) 可以选取一种基状态,其他状态可以由基状态修改而来,这时候尽量把问题改成多个量选 / 不选的问题,也尽量把他们的影响独立开来,然后用 dp 决策这个过程:New Year and Binary Tree Paths / Tutorial
- 题目特点:和二进制位相关的计数,从某种方面体现出独立性
- 思路:既然是按位相关,“位”本身就是一个很相互独立的东西,所以我们要找一种合适的方式将位独立开来。在这道题中,我们进行了两次独立操作:第一次我们将“从上到下走”这一过程独立成了“每一层是否选择向右走”,然后发现每层独立,很好算贡献;第二次我们将“\(2^1-1,2^2-1,\dots,2^k-1,\dots\)”变成了“\(2^1,2^2,\dots,2^k,\dots\)”,这样它在值上也就独立开来了,很方便数位 dp。
- \(\color{red}\bigcirc\) 如果是决策的最终状态,且有多种方式可以到达同一种最终状态,那么强制只用其中一种方式:Mr. Kitayuta's Gift / Tutorial
- 题目特点:求有多少种符合要求的序列,但是判定一个序列的过程是不确定的,也就是会算重
- 思路:像这种问题,我们其实可以考虑贪心,也就是如果判定方法是贪心的话就可以去 dp 贪心的过程。换句话说,考虑我们判定一个序列是否合法的程序,如果它没有比较复杂的搜索-回溯之类的操作,那么我们的判定程序会在某些适宜的时候
break
,我们就可以考虑针对break
的位置进行 dp。以这道题为例,我们考虑判定一个回文串是否满足条件的时候,我们可以依次剥掉最左和最右的字符,如果它们是询问字符串两端的字符就可以对应地删掉,到最后如果删空则满足条件。这个过程是一个很好的贪心过程,我们的解法也就是照着这个过程进行 dp 的。 - \(\color{blue}\dagger\) 优化矩阵快速幂优化 dp 的技巧:将 dp 的转移过程视为在一个自动机上跑的过程,那么问的就是从起点走到终点走 \(n\) 步的方案数,自然是可以采用矩阵快速幂加速的,但是这还不够。我们可以进一步研究自动机的性质,尝试压缩这个自动机,直到自动机的大小在 \(O(s^3\log n)\) 可以接受的范围之内为止。以这道题为例,我们首先发现一条路径的权值只和经过的 \(24\) 自环点数量和 \(25\) 自环点数量有关,所以可以预处理出来每种路径的数量;进一步发现这 \(m\) 种路径是可以合并的,于是把这 \(m\) 种路径合并到同一个自动机上,我们就可以 \(O(m^3\log n)\) 通过本题了!
1.1.4 选定dp主体
我们知道,dp 的本质是枚举所有状态,但是这些状态必然有一个载体,所以我对 dp 主体的理解是他只是一种状态的表示方法,我们用它来描述状态,但是状态是最重要的。
很多时候你会遇到很多奇怪的 dp 主体,但判断它们的唯一原则就是是否能考虑所有状态,选 dp 主体的时候容易被思维定式局限,所以思考可以从哪些方面来描述状态是重要的,这里有一些关于选定 dp 主体的例子:
- CF1474F / Tutorial:选 x 轴为 dp 主体是不容易的,但是因为我们通常是按顺序考虑子序列所以很容易陷入这个误区,选 y 轴为 dp 主体问题就迎刃而解了,所以对偏序关系更高级的理解是若干个限制,不同的题需要从不同的限制入手。
- 题目特点:和最长上升子序列有关的 dp 问题
- 思路:最长上升子序列相关的 dp 必然需要考虑的一点是扫值域而不是扫坐标。虽然这道题是最长上升子序列的计数,也可以沿用这种思想。由于坐标范围较小,所以本来需要扫很大的值域,现在分成了若干小段,每段可以用矩阵快速幂优化,所以是可做的。
- 实现注意:一定要想清楚矩阵快速幂优化的段范围是多少。例如这道题中是只能对离散化后的每个开区间 \((l,r)\) 内进行快速幂处理的,因为端点处的转移和内部的转移不尽相同。
- 卡农 / Tutorial:并不一定以小处为主体,比如这题主体为数不好做,但是主体为集合就可以直接转移了。
- 题目特点:要求各个元素之间互不重复的 dp
- 思路:我们很讨厌互不重复,所以考虑容斥掉这个限制。不过其实这个容斥还有一种更简单直接的描述:记 \(f_i\) 表示前 \(i\) 个数满足互不相同的方案数,然后每次往后面加一个数的时候考虑减掉这个数可以与已有的数相同的影响,如果这样可以 dp 就能做到线性了。
- CF1463F / Tutorial:
遇到集合最优化问题时,可以考虑在值域上规划,选值域为 dp 主体。(?)- 题目特点:在序列上的 dp 问题,但是只关心距离不超过 \(k\) 的位置之间的关系,并且代价具有高度的重复性。
- 思路:一个重要的思想:尝试证明选择的部分存在长度为 \(k\) 的循环节时最优。这道题发现最优解一定存在长度为 \(k\) 的循环节后就做完了。参考资料
1.1.5 设计dp状态
-
只保留和代价计算有关的量,比如如果代价只和数量有关,并且如果可以通过数量还原出集合,我们可以把状压的一维改成线性的:CF1188D / Tutorial
- 题目特点:和二进制有关,和进位有关。
- 思路: 1. 和二进制有关,必然按位考虑 2. 和进位有关的问题,两种考虑方式,一种是进位后低位全 0,一种是记录哪些位置有进位。
-
当状态很大的时候可以考虑通过枚举来把某些量拿出状态里面:[Game with Cards][https://codeforces.com/contest/1539/problem/E] / Tutorial
- 题目特点:物品的状态具有很明显的延时效应,也即一个物品的状态,如果不去碰它,它在自然情况下是不会改变的
- 思路:对于这种具有“延时效应”的题目,设状态的时候如果把每个物品的当前状态都放在里面会显得有些浪费,所以我们可以类似于枚举上一次改变的时间,这样就可以把庞大的状态数给压到枚举的复杂度上面,然后再考虑用数据结构优化了。
-
当问题前后相互影响时,可以考虑把全局变量定义到局部状态里面:密室逃脱 / Tutorial;如果操作是全局的,上面的方法也很好用:Red and Black Tree / Tutorial
- CF375E 题目特点:树上问题,有若干个物品,每个物品需要一个标记来控制它,不同物品可以共用一个标记,求最小标记数。能控制的限制是有偏序关系的,如距离等。
- 思路:由于“能控制”是有偏序关系的,所以可以设状态 \(f(u,i)\) 表示 \(u\) 被 \(i\) 控制,且只考虑 \(u\) 子树中标记点的数量的最小值。这样从子树合并过来的时候可能会有一个问题,就是 \(u\) 需要一个子树外面的点 \(i\) 来控制,\(v\) 需要一个子树外面的点 \(j\) 来控制,\(i,j\) 还不相同,那这个状态就没法玩儿了。但是这个“偏序关系”应该能够保证不会出现这种情况,也就是能够保证 \(v\) 不如直接被 \(i\) 控制即可。
-
把和限制紧密贴合的量设计到状态里面,阴间出题人可能会用题目描述来引导你设计非常慢的 dpdpdp 状态,做一些基本的题意转化即可:皮配 / Tutorial
-
可以设计相反的状态,比如最小花费步数转化为最大减少步数,可能转化后更贴合题设:Paint
1.1.6 确定dp顺序
听着,所谓 dpdpdp,最重要的是顺序。无论是考虑限制的顺序,还是计算贡献的顺序,一定要着重思考,我觉得它和结论题中的必要条件有着相同的地位。
- 治疗计划:如果操作是有时间顺序的话,我们很容易被局限于时间中,另一种思路是考虑操作的主体,考虑主体的关系时把时间的影响考虑进去,所以 dpdpdp 不一定按时间。
- 一拳超人:如果一个东西对另一个东西有单向的影响,那么先处理前面那个有助于考虑影响。
- Kyoya and Train:图问题中,要注意转移顺序,通常这一维时单调的就可以作为顺序维,比如时间。
- 集合选数:dpdpdp 顺序尽量让限制紧凑一些,我们也许可以在独立的前提下篡改顺序。
- Singer House、环:注意 dpdpdp 的顺序和方案的顺序是有区别的。比如对于路径计数问题,如果我们按照序列从左到右 /// 树形自底向上的 dpdpdp 顺序计数,那么达到的效果就是若干条有向链合并的过程。
- MEX counting、遗迹:可能需要根据限制的特性,提前///延迟确定一些元素的过程。
- The Hanged Man:选取合适的 dpdpdp 顺序,可以减少 dpdpdp 状态。
1.1.7 寻找子问题
- 这部分我觉得可以总结一些核心的思想:寻找子问题最重要的是找状态之间的相似性,所谓相似性的含义就是信息记录在子问题中的一部分的占比,相似性越大你写转移就越容易,这需要你强大的观察能力:stars;要去构造问题之间的相似性,比如染色问题中可以通过钦定不变色来获得相似性:Shrinking Tree
- 寻找子问题要考虑消除后面操作的影响,这样才能保证没有后效性:如果某个元素对于前面的影响是长远的,但又只能考虑一小步转移,那么寻找当前问题的等效子问题,就可以把这个影响传递下去,完成转移:stars;或者可以通过枚举法来消除所谓影响:Lanterns
- 当考虑一小步不能很好的归纳到子问题时,可以定义一个辅助数组来解决这个中介状态:矩阵、统计问题中若当前不能计算代价,可以设计辅助数组把代价存下来:消失的运算符
- 枚举法制造限制来划分子问题,比如顺序匹配问题中可以枚举空位:CF1439D
1.1.8 考虑如何转移
-
转移的大步小步。小步适于考虑转移,但是可能会消耗更多时间;大步常常会很复杂,但是可能起到加速的效果。在大步小步之间切换,才能写出合适的转移:Appleman and a Game
-
转移中存在的限制很烦,在一些计数问题中,对于多个元素的限制可以考虑某些元素任意选,另一些元素为了满足限制而具有确定的选法,计数 dpdpdp 中选择的顺序是十分重要的:卡农
-
如果确定转移顺序之后前后会相互影响,那么在后影响前的时候可以通过枚举来解决这个影响,再归纳到子问题:CF1476F
-
多个对象的决策问题中,通常只考虑最后一个对象的决策:CF1439D
-
正难则反是很重要的技巧,比如在计算
第一次到达的方案数
的题目中,容斥掉的项就是子问题:逃跑 -
转移的时候适当的算错,可能会让转移简洁很多:常见的模型是算错但是一定不会作为转移点 link;还有是算少但是最优解可以被统计到:Gerald and Path
-
复合 dpdpdp,设计多个 dpdpdp 状态,互相转移的方法是很值得思考的。其实我感觉它的原理还是分步思想,把一个较为复杂的问题拆分成若干个部分解决,这些部分中可能又蕴含子问题:模拟赛6-A;或者是多个 dpdpdp,一个 dpdpdp 用于拆分,一个 dpdpdp 用于解决被弱化过的问题,通常可以得到很好的性质:CF1439D、泳池
1.2 常见dp优化方式
1.2.1 斜率优化
- 有些题很难看出需要用斜率优化,把和状态有关的量当成常数,把只和转移点有关的量当成纵坐标,把交叉项系数当成横坐标,把转移点的某个量当成直线去切即可:CF1067D
- 另一类不常见的斜率优化是,变形出斜率的形式,维护凸包:国王饮水记
1.2.2 决策单调性
定义:对于 a<b<c<da<b<c<da<b<c<d,若 f(c)f(c)f(c) 从 bbb 转移比 aaa 优,那么 f(d)f(d)f(d) 从 bbb 转移也比 aaa 优。
判断式:w(j,i+1)+w(j+1,i)≥w(j,i)+w(j+1,i+1)w(j,i+1)+w(j+1,i)≥w(j,i)+w(j+1,i+1)w(j,i+1)+w(j+1,i)\geq w(j,i)+w(j+1,i+1)
通用实现:把决策点拿去分治,每次更新中间位置的状态,然后把决策点划分到两边。
- 一些最值问题可以套用决策单调性模型:课桌、Evacuation
1.2.3 考虑有效转移/状态
- 只去计算和答案有关的状态(也就是减少状态的数量),当前前提是转移不出问题:Rescue Niwen!;如果答案是和式,那么可以把状态也定义成和式:Student's Camp
- 在某一种特殊情况下一种转移的方式会变少,可能达到复杂度级别的优化。序列上可以考虑区间交///不交情况下的有效转移:Communism
- 两种转移的时候选一种为主体,另一种在它的基础上对答案有影响的时候就是有效转移;并且如果一种状态明显超过了需要考虑的范围(就算在价值上它是更优的),你也不需要去考虑它:CF771E
- 从某个点转移一定比另一个点转移要优,看起来很朴素的结论却能在很多时候有奇效。它可以让转移点的数量大大减少:Bank Security Unification;也可以忽略到一些恶心的限制(不合法的点一定不优):模拟赛4-A;也可以利用贪心大致筛选一些有效转移:Professional layer
1.2.4 讨论法
- 整个问题重要的量可能很多,都需要用状态表示出来,但某些情况下有用的量可能会减少,这时候可以分类讨论,互相转移:Favorite Game
- 当 dpdpdp 的取值很少且是分层转移的,可以讨论并用数据结构维护状态:Split
- 图论问题可以讨论掉一些较小的环来降低复杂度:Abandoning Roads
- 讨论法很适合处理环形 dpdpdp 问题。环形 dpdpdp 的关键是:断开哪一条环边(我们想要环边具有怎样的特殊性质);断开环边局部的状态是怎样的(简单讨论可以解决):Sonya Partymaker
1.2.5 等价类
- 在很多图论问题中状压环的时候,如果转移只和环的大小有关,那么可以把相同大小的环看成一个等价类:CF1466H
- 只和数量有关而和顺序无关可以按照数量划分等价类:Mr. Kitayuta's Gift
- 乘积不超过某个数的题目可以考虑通过逆向整除分块来划分等价类:Ridiculous Netizens
- 转移方式相同而且易于计算总方案数,可以把它们划分为等价类:逃跑
1.2.6 数据结构优化
- 对于数列覆盖问题,常有的结论是两个点的覆盖范围要么包含、要么相离,这时候可以选择用单调数据结构维护(因为覆盖范围单调),而不是带 loglog\tt log 的数据结构:CF1131G
- 发现题目有不可解决的维度时,要敢于使用数据结构。但是此时空间消耗特别重要,注意处理 dpdpdp 的顺序,才能时数据结构的使用简单化,并且减少常数的消耗:购票
1.2.7 解决巨大数据范围
- 离散化是解决较大数据范围的一种方式,如果某一段内转移相同,可以离散化出段然后矩阵加速:CF1474F;或者记录离散化段内的信息转移:划艇;离散化还可以把连续型问题转化为离散型问题:Random Ranking
- 可以找循环节,只要不同的循环节之间没有多的限制就行,然后每个循环内部都取最优解。证明的关键在于没有构成完整循环的那部分,可以考虑调整法证明(具体思路见例题):CF1463F
- 对于最优化的 dpdpdp 问题,可以去找转移点的结论,知道每个转移点转移多少次就可以矩阵加速了:CF1067D
1.2.8 考虑转移路径
- 转移路径可以形象化地表示出来,那么可以把简单的 dpdpdp 转化成计数问题,比如这里二维简单 dpdpdp 转网格图计数:[JLOI2015]骗我呢
- 考虑 dpdpdp 的组合意义可以建出图论模型,快速计算时直接枚举其中的某个量:匹配字符串
1.2.9 费用计算优化
- 如果当前状态不好知道,但你清楚代价的变化规则时,可以费用提前计算:Candles
- 如果代价是全局的,那么可以费用延后计算(但一定要注意有无后效性啊??):Red and Black Tree
1.2.10 分治优化
- 背包问题中如果只有一种元素会特殊,那么可以用分治优化,每次保留本层最优解传递上去即可,复杂度会优化的原因是分治会通过在部分中竞争保留最优解:篮球;
- 只要是对于一段区间只有加入就可以尝试线段树分治,不一定要是序列上的区间,可以是先建立树形结构之后,对于一段 dfndfn\tt dfn 区间的修改:序列
- 如果只有一个位置不加入,也可以直接分治优化:Random Ranking
1.2.11 神奇优化
- 当转移代价无法优化时,可以考虑转移点数量的限制,然后快速找到转移点:绯色IOI
- 不支持取 maxmax\max 操作时,可以通过考虑转移点的性质来转贪心,通常是权值单向影响的题目:CF878E
- 如果 dpdpdp 值单调,并且需要支持合并和取最值,那么可以用 setset\tt set 维护差分标记:魔法树
- 如果 dpdpdp 代价为正并且只存在于单点上,那么可以考虑成类最短路,每次拿到最小的那个能扩展就扩展,用数据结构维护最容易被扩展的点即可(通常是线段树维护最值):治疗计划
- 整体 dpdpdp,如果多个 dpdpdp 转移方式完全相同,那么可以考虑一次 dpdpdp 求出答案,比如序列上可以通过端点初始化和端点统计完成:Extreme Extension、Foreigner、星图;更加灵活的形式是找问题之间的关系:Palindromic Hamiltonian Path;其他例子:Mr. Kitayuta's Gift、山河重整、线段树
1.3 常见dp模型
1.3.1 树形dp
- 树背包真正的复杂度是第一维大小乘上第二维大小,特别是第二维很小的情况:CF1097G
- 建立一些使用于特殊性质的树形结构,并在结构上规划:浅谈笛卡尔树结构的应用
- 树重排规划问题可以考虑转区间 dpdpdp,本质上就是枚举根的过程:Tree Tweaking
- 路径规划问题,可以枚举起点,然后使用换根 dpdpdp 来获取最优的起点:Svjetlo
- 合并问题可以从叶子开始考虑:Logical Operations on Tree
- 树上路径的限制问题,可以只保留最深的///最浅的记为状态转移:命运
- 不支持合并的树形 dpdpdp 问题可以考虑转成 dfndfn\tt dfn 序上 dpdpdp(树上依赖背包):Ridiculous Netizens
1.3.2 状压dp
- 图计数问题,通常是考虑导出子图,常用的技巧是正难则反:浅谈状压dp在图计数问题上的应用
- 如果只需要知道大小关系,状压 dpdpdp 也可用于确定序列问题上,需要多消耗 O(O(O(值域))) 的复杂度:Random Robots
- 状压套折半搜索具有神奇的复杂度 O((1+2–√)n)O((1+2)n)O((1+\sqrt 2)^n):Harry The Potter
1.3.3 连续段dp
- 连续段 dpdpdp 可以计算端点产生的贡献,这是和连续段数正相关的:摩天大楼
- 写转移的时候可以用两段之间任意长这个条件,方案数就便于计算了:Phoenix and Computers
1.3.4 背包
- 如果装入的总物品不是很多(n−−√n\sqrt n 级别)并且连续,可以考虑柱状图 dpdpdp,转移分为增加一个柱子和把所有柱子整体升高:逆序对、山河重整;而且这种 dpdpdp 天然带有顺序,如果你想求前 kkk 大的话是特别方便的:绿宝石之岛
- 多重背包如果只需要判定存在性,可以维护还剩下的物品数量,转完全背包:AB tree
- 如果背包支持快速合并(有凸性),那么可以直接用分治优化:妹妹卡组
- 要有一个总体是时间观,很多巧妙的背包优化只能把 nnn 变成 n−−√n\sqrt n,如果需要本质复杂度的下降,那么可以尝试改变维护的东西,降维是常用的技巧:Tree Degree Subset Sum、Jumping sequence,降维的关键就是找独立性之后拆分:皮配
- 有一个物品是特殊的,它的选取方式和其他物品不一样,特殊考虑它就能让问题变简单:Lucky Numbers;少量特殊物品时可以考虑分别计算然后合并背包:皮配
- 很多时候背包会存在(隐藏的)拓扑关系,这时候的结论可能是选了小价值物品就必须选大价值物品:Turtle
- 总容量大,但是物品重量很小的背包,可以按二进制位考虑压缩有效状态数:物品
- 前后缀背包,把这个思想搬到树上就是求出 dfndfn\tt dfn 正序背包和 dfndfn\tt dfn 倒序背包,然后再合并:苹果树
1.3.5 计数dp
计数 dpdpdp 的原则是:初始有一些基础方案,然后我们逐步添加可以区分出方案的东西(这东西是根据方案不同的定义来的),转移到不同的地方就代表这一步产生了不同的方案:高维游走
- 组合意义的嵌套的重要的方法,也就是我们给我们的代价函数确定一个组合意义,那么 dpdpdp 就需要同时完成确定局面和组合计数的功能,常用技巧是拆分:Pass to Next、Stranger Trees、Crash 的文明世界
- 巧妙的枚举可以达到合并两个子问题方案的目的,比如本题枚举可以合并组合数:CF1097G
- 如果判断合法需要使用 dpdpdp,而原问题又是一个计数问题时,可以使用 dpdpdp 套 dpdpdp,其关键还是建出 DFADFA\tt DFA:模拟赛7-A、游园会
1.3.6 杂项
- dpdpdp 维护直线函数:CF889E
- slope trickslope trick\tt slope\ trick 优化 dpdpdp,主要处理代价带绝对值的规划题目:折线算法;可以维护很多跟斜率有关的操作,比如含有 maxmax\tt max 的函数:Increment Decrement
- dpdpdp 维护容斥系数,在大小关系中的计数题中十分常见:小D与随机、不等关系;一些需要考虑集合的毒瘤题中,暴力指数级容斥出奇迹,再转 dpdpdp 维护即可:猎人杀、百鸽笼
2 图论
2.1 图论问题的思维过程
2.1.1 图论模型的建立
首先考虑对于原问题的什么对象建立模型(主体的选取),然后尝试用图论的各种意义去表示原问题中的元素(比如点权、边权、路径、连通块.........),这两步都不要被定式思维所限制。
以题目中的一个限制为基础建立模型,这一步不要被思维定式所局限。
- 遇到难以处理的全局限制时可以考虑图论:Maximum Adjacent Pairs
- 矩形中类似推箱子的问题,可以把箱子的移动转化成空格的移动,建立关于空格移动路径的图:Shifting Dominoes
- 区间元素和的判定问题可以把前缀和建成点,原图中的元素建成连接前缀和的边:Flip and Reverse
2.1.2 图结构的分析
- 树形结构具有很多优美的性质,可以通过分析度数与环来证明树形结构:Shifting Dominoes
- dfsdfsdfs 树是很重要的思维方式,通常我们分析树边和非树边能得到很多有意思的性质。如果是有向图还要考虑哪个点为根更好分析:James and the Chase;或者可以通过 dfsdfs\tt dfs 树直接给出构造:Nezzar and Hidden Permutations;Weighting a Tree
- DAGDAG\tt DAG 具有很好的性质,所以就算是遇到一般图时,也可以先思考在 DAGDAG\tt DAG 的环境下如何处理:Sergey's problem
2.1.3 问题转化
- 度数和连通性常常可以互化,但度数描述单点限制,连通性描述整体限制:电报
- 图论中拆分思维也很重要,把总贡献拆分到点上///边上:Keys
- 如果不同的限制具有拓扑关系,根据题目特性可能可以忽略一些限制:Falling sand
- 一类东西只贡献一次的问题可以考虑转化成匹配问题:Maximum Adjacent Pairs
- 可达性问题可以考虑转化成连通性问题,此时注意考察连通的双向性和等价性:Cow and Vacation
2.1.4 寻找结论
- 如果点权集中可以获得更多贡献,那么可以思考答案会不会是原图的最大团:七负我
- 从小处入手思考结论(比如叶子),那么你可能发现原来复杂的问题变简单了:绯色 IOI(抵达)
- 拓扑覆盖问题可以考虑在原序列上的区间性质:Falling sand;连通性问题也可能有区间性质:Number of Components
- 通过双图策略寻找性质:String Transformation 2、Next or Nextnext
- 生成树的应用是广泛的,关注性质
答案一定在满足某种性质的生成树上
:模拟赛6-B
2.2 常见算法应用
2.2.1 最短路
- dijkdijk\tt dijk 的遍历思想在很多题中有大用处,如果每个点只需要遍历一次那么维护最有可能遍历的点:Mike and code of a permutation;治疗计划
- 动态加边可以解决到达时间最小的限制,它的本质是如果具有连通性就可以说明最早到达:Dirty Arkady's Kitchen;动态加边还可以直接维护强连通性,对正反图动态 bfsbfs\tt bfs 即可:图函数;动态加边还可以完成版本之间的转化,结合 spfaspfa\tt spfa 可以做到某些情况下的动态最短路:道路堵塞
- 用 dijkdijk\tt dijk 可以保证一些特殊的转移顺序:Intergalaxy Trips
- 环上的最短路,如果数据范围极大,先考虑重构环之后再扫环:Drazil and His Happy Friends
- 删点后求最短路的问题有固定套路,就是考虑有一条边一定会跨过这个点,可以拼凑出路径来:风之轨迹;道路堵塞
2.2.2 连通性
- 互达问题可以思考连通性,连通性重要的一点是传递性,有些问题可能只有特殊点具有连通性:Cow and Vacation
- lctlct\tt lct 可以维护动态边双连通分量,考虑把非树边的影响转化成赋值标记即可:Bear and Chemistry
- 无向图路径的最值问题,可以将边权排序之后转化为维护连通性(最小生成树只是特例):路径查询
- 对于带修改的问题来说,可以有一个统一的固定结构来处理两点连通的时刻(边起作用的时刻):Gates to Another World;维护强连通性,可以用整体二分求出每条边强连通的时间,然后转化成维护无向图的连通性:WD与地图
2.2.3 拓扑排序
- 拓扑排序可以提供一种解构图的顺序,注意拓扑排序中天然带有的可达性:Pink Floyd;如果需要一些出现顺序的关系,可以先建立图之后考虑其拓扑序:Insider's Information
- 拓扑排序的性质,同在队列里的点没有任何到达关系,可以通过它来筛选合法点:Upgrading Cities
- 拓扑排序的另类形式:按 1∼n1∼n1\sim n 的顺序在反图上 dfsdfs\tt dfs,最后回溯的顺序就是拓扑序:Mike and code of a permutation
2.2.4 将问题转化到图论算法
- 调整法可以帮助建出差分约束限制,矩阵问题可以以行和列为待定变量:矩阵游戏
- 拓扑排序可以解决带平局的博弈问题,首先全部设置为平局,按照定义对反图跑拓扑即可:Shiritori
- 差分约束的反向应用,如果要求是不出现负环,那么等价于有差分约束的合法解,那么可以把图上的边都写成不等式:Negative Cycle
- 二分图染色问题,如果需要优化连边,得到同色的性质可以缩成一个点,然后套用并查集路径压缩的时间复杂度:港口设施
2.3 树问题
知识点:树的直径(邻域理论)、树的中心、链剖分、虚树、树分治、树合并、树哈希。
2.3.1 问题转化
- 无根树问题定根(比如路径、删叶子)是一种重要的思维方法:Squid Game、D、Matches Are Not a Child's Play;考虑重心可以去掉一些关于子树大小的 minmin\min 式子:Distance Matching
- 树上最远点问题可以转直径端点:CF516D、广义的直径问题是带权匹配,是支持点权的:情报中心;最长链问题也可能和直径有联系:模拟赛7-B;以直径中点为根建树可能是不错的选择:Drazil and Morning Exercise;以直径端点建树也可能有很好的性质:Spiders Evil Plan
2.3.2 树上算法
- lctlct\tt lct 有着特别重要的染色模型,也就是用实边代表同色,虚边代表异色,修改就是把一个点到根的路径染色,然后用数据结构维护颜色的方法:Matches Are Not a Child's Play、树点涂色
- 延迟贪心,即能不放置关键点则不放置,这样能把关键点放置在最浅的位置:Squid Game
- 要快速计算虚树的大小时,可以考虑下标为 dfndfn\tt dfn 序的线段树来维护:语言;这种用线段树去重的方法还可以应用于字符串中(若干后缀的本质不同前缀,samsam\tt sam 上启发式合并):Asterisk Substrings;这类线段树合并的本质是用线段树的结构提供了一个合并顺序:三角形
- 最大///最小边权问题考虑 kruskalkruskal\tt kruskal 重构树。比如想要求虚树的最大边权,可以转化成重构树上最浅的祖先,即 dfndfn\tt dfn 序最大点和最小点的 lcalca\tt lca:Groceries in Meteor Town
- 路径问题考虑点分治,不要被复杂的形式给诈骗了:Network
2.3.3 常见模型
- 连通块问题的常用解决方法:可以在连通块的最浅点统计这个连通块的贡献,那么用树形 dpdpdp 来规划这个最浅点即可:切树游戏、Ridiculous Netizens;还可以用
点数-边数=1
的经典容斥:完美的集合;选取连通块的代表点时,可以套用点分树这种分治结构:成都七中、Ridiculous Netizens - 还原树的问题中,可以分步还原,也就是先还原特殊点的结构,再还原整体的结构:Restoring Map;New Year and Forgotten Tree
- 边权和贡献最大问题可以往长链剖分这个角度考虑,如果是路径问题可以通过 2k2k2k 个叶子的构造性结论转化成最长链问题(前提是需要定根):Spiders Evil Plan
- 巧妙利用树上结构,如可以用重链剖分的结构来快速定位:Nauuo and Binary Tree;若是多个数通过操作合并为一个数,可以思考操作的树形结构:Eternal Average;括号序列的树形结构是重要的:[省选联考 2022] 序列变换(差点因为这题没进队)
2.4 网络流
有一些入门的内容,不想整理了:网络流简单题选做;网络流二十四题
把下面这些整理完之后,我才发现好像网络流确实已经很难有新意了,很多 tricktrick\tt trick 都是反复出现的。
2.4.1 量的意义
- 用流量的流入和流出代表加减,可以表示一些加减的不等式关系:Red-Blue Graph
- 流量可以在基于要求的情况下,表示解决要求的途径,而费用可以看成途径的代价:Chips Challenge
- 可以用路径来表达不合法的关系,再用最小割来获取最优解:Starry Night Camping;老C的方块
- 对于覆盖类型的限制,把需要覆盖的点串联起来,用路径表示覆盖的关系:奇怪的线段树
2.4.2 观察性质
- 如果用网络流解决平面图问题,可以考虑黑白染色转二分图的性质:过山车
- 网络流解决矩阵问题,可以用行列来建二分图:;但这有时候是个思维定式,如果行和列独立并且有其他限制,那么建单层图能更好地表达限制:Asa's Chess Problem
- 完美匹配的等价条件是 HallHall\tt Hall 定理,所以如果原问题和 HallHall\tt Hall 有某种关联可以通过这层关系转化到网络流上:Construction of a tree
- 拆分法,网络流一般只能解决单点对单点的限制,如果是多点对单点,那么大胆找结论,我们可以从答案的形式入手:Rainbow Triples;还有对代价的拆分,比如绝对值的微元贡献法:One Billion Shades of Grey
- 如果某个问题可以建出其费用流模型,那么是有凸性的,就引申到了 wqswqs\tt wqs 之类的算法:April Fools' Problem
2.4.3 小技巧
- 单点的多重意义,可以考虑拆点,通常可以让限制表达得更明晰:过山车,Making It Bipartite
- 动态网络流,如果有多个图但是差别不大,可以把多出来的边退流:模拟赛7-C;One Billion Shades of Grey
- 手算最小割,常常需要枚举法和数据结构维护:Rainbow Triples;也可以对于点所属的颜色来 dpdpdp:Breadboard Capacity;手算最大流也常用,使用结论然后数据结构维护:k-Maximum Subsequence Sum
- 保留有用的边,减少边数:Bridge Club;划分等价类,减少点数:小埋与游乐场
- 优化建图,本质和图论的优化建图是同一类方法,cdqcdqcdq 优化建图:通信;主席树优化建图:a+b Problem
2.4.4 图匹配问题
- 一般图匹配也许可以通过结论转化成二分图匹配(左部右部的匹配次数相同):模拟赛5-A
- 一些情况下贪心地匹配:Add to Square(达到构造的界);模拟赛2-C(特殊的偏序关系)
- 二分图的独立性(只需要考虑某一部的具体情况):Bipartite Blanket
- 不能走回头路的博弈问题可以考察二分图匹配相关的结论,这是因为交错路具有特殊的数量关系:游戏
- 经典问题:有向图求环划分,可以拆成入点和出点跑二分图匹配:边
2.4.5 常见模型
- 混合图的欧拉回路,思考清楚度数在原问题中对应着什么即可套用此模型:wait
- 最大权闭合子图,可以解决带强制选取关系的选///不选问题,或者也可以解决类似的二选一问题:魔法商店
- 最长反链,转最小链覆盖之后,用 dfsdfs\tt dfs 的方法构造即可:Birthday;也有构造新的偏序集再跑最长反链的题目:Making It Bipartite
- 上下界网络流,可以表示一些强制限制:Showing Off;带上下界的网络流
3.unknown
3.1 计数
3.1.1 问题转化
- 映射法,遇到多对一的问题时(多种方案生成同构的结果),可以通过添加限制把多化一,构成双射:Two Histograms;可以根据数感,和常见的结构建立映射关系:神必的集合、Slime and Sequences;遇到信息题时(确定未知变量),考虑我们都知道什么信息,和信息建立映射关系:Bears and Juice
- 组合意义法,如果方案数是加权的,那么思考权值的组合意义,最好化为存粹的方案数:Min Product Sum;教数学的校长;组合意义尽量合并多步计数(即把原来复杂的结果化为一个过程):盒;对于外层的枚举可以建立虚点来等效替换:数叶子;逆向应用一些定理也是组合意义的一个方向:无损加密
- 把限制转化成单侧的,这样计数顺序会更明显:Centroid Probabilities
- 切换计数对象,简化计数的主体:stairs;去除不易处理的限制:count;添加限制转化为容易计数的对象:Two Pieces
3.1.2 容斥/反演
- 最常见的容斥方法是枚举不合法的个数:Two Histograms、神经网络
- 如果存在选取个数 ≤ai≤ai\leq a_i 的限制,可以强制选取 ai+1ai+1a_i+1 个,然后记上 −1−1-1 的容斥系数:钥匙
- LGVLGV\tt LGV 引理,原本求的是偶数逆序对的不交路径减去奇数逆序对的不交路径。在特殊的图中(比如网格图,数轴)可以直接求不交路径数量:无损加密,如果终点未知还可以考虑 dpdpdp 计算行列式:Random Robots
- 容斥的另一种思考方向,先给出一种会算重的计数方法,然后找出这种计数方法算重的原因,然后对这个算重的原因容斥:树;比如经典模型 DAGDAG\tt DAG 计数,就是容斥入度为 000 的点:Finding satisfactory solutions;类似的基环树计数,可以容斥叶子:人类补完计划
- 集合划分容斥,用于解决相等关系的限制,直接对边数容斥即可:Distinct Multiples
3.1.3 统计方法
- 思考答案的形式,然后思考在哪里统计,如果答案是区间则可以在端点处统计:Chords;如果答案是树上的点集,那么可以在其 lcalca\tt lca 处统计(写成代码就是在合并子树的时候统计):Vladislav and a Great Legend
- 思考统计的顺序,确定合理的顺序可以让式子变得更简单,通常有偏序关系就意味着可能需要思考顺序:Inversions;分步统计,逐步确定的方法也是重要的:钥匙;为保证顺序,还可以在一次计数中结合不同的计数方法:模拟赛3-A;可以先确定一个大致的顺序,然后在转移时修正它:Square Constraints
- 关于去重方法,可以考虑给同构的方案增加限制:Piling Up;比较无脑的去重方法,考虑一种方案会被计算多少次,然后用除法去重:Fox And Travelling;如果要考虑无序的子结构,可以考虑隔板法去重:Shake It!
3.1.4 常见模型
- 卡塔兰数模型,可以解决在网格图中行走但是不越过某条直线的方案数:Ball Eat Chameleons
- 斯特林数模型,可以在 nnn 大 kkk 小的情况下优化计算 nknkn^k:Vladislav and a Great Legend
- 本质不同字符串计数的问题,直接考虑建立 DFADFA\tt DFA:Strange Operation
3.1.5 概率期望
- 注意利用等概率的性质,比如转等概率环,可以把总体的方案放到单点上去:Wine Thief、也可以方便地计算总方案数:AmShZ Farm
- 如果题目保证了出现的概率,那么这题可能就是基于概率来寻找关键点:James and the Chase
- 拆分。独立变量的概率常常可以拆分,这样可以分步解决问题:Strongly Connected Tournament;概率对期望的拆分十分重要,可以完成期望对概率的转化:Shuffles Cards、Gachapon
- 概率期望类型的题目中,一些前缀 /// 差分类型的转化往往能够简化问题:地震后的幻想乡
3.2 基础方法
3.2.1 势能法/均摊法
- 适合势能法的题目有一些特征,比如覆盖问题:小Z与函数;染色问题:Intervals of Intervals、亿块田(位运算可以看成数位的颜色段均摊);区间合并与分裂问题(通常是维护的东西具有单调性,可以把值相同的一段看成区间的题目):货币、Holy Diver
- 使用势能法时,可以从一些感性的角度,通过定义势能函数来入手:Souvenirs
- 尽管有时候操作比较复杂,但是若是具有
某变量一定减少
的性质,就可以通过加速一些简单的过程来优化:Addition and Andition;可以通过一些简单的操作变换,使得减少量和花费的时间之间对应起来:五彩斑斓的世界 - 势能线段树,可以维护区间取 minmin\min、区间除法等操作:浅谈势能线段树在特殊区间问题上的应用、Cartesian Tree、Stations
- 均摊法最重要的是分析总体复杂度,所以一些暴力的操作可能看起来很慢但是总体复杂度优秀:Berland Miners
3.2.2 拆分法
使用拆分法时,最重要的是保证独立性。
- 如果题目中存在时间顺序,可以把这个顺序破除来保证独立性(即换一个顺序计算):Addition and Andition
- 可以对左右端点拆分来优化,比如应用到区间 dpdpdp 问题:Student's Camp;可以拆分之后分别计算:Cartesian Tree
- 高维问题可以考虑拆分成独立的低维问题:Berserk Robot、Jumping sequence
- 拆分法可以用来集中限制,比如把两个主体间的限制拆开,等效到一个主体上:Drazil and His Happy Friends;拆分法还可以分解限制,可以把区间限制拆开,分解到单点上:模拟赛2-B
- 线段树可以理解成对二进制数位的拆分,有些题目可能利用线段树来拆分:Xor-Set
3.2.3 调整法
调整法的使用过程是:选取初始状态(Sergey's problem)、确定调整方式。
- 有很多匹配问题具有神奇结论,证明可以考虑调整法:Modulo Pairing、Bear and Cavalry
- 如果某问题连判定合法性都困难,还要求你最优化的话,那么大胆使用调整法。从最优状态开始调整,使用最小代价来达成合法性(这样可以两个愿望一次满足):Two Faced Cards、Chips Challenge
- 邓老师调整法,从一个合法状态开始,然后对它进行微调使得权值变大 /// 变小:邓老师调整法、Pastoral Oddities
- 可以用调整法来研究最优解满足的性质,这时候使用一些简单的不等关系是重要的:遇到困难睡大觉、转盘、Max Correct Set
- 两种决策的问题可以考虑主一副二的方法,先固定一个,然后用另一个调整以获得最优解:Squares
- 连续型问题向离散型问题的转化,可以通过调整法证明只会取到端点值:Parametric MST、Levko and Game
3.2.4 分治法/倍增法
- 矩形问题可以考虑交替分治,加上点双指针单调性什么的可以做到优秀复杂度:Empty Rectangles
- 类似后缀数组的倍增技巧,优化的重点是充分利用上一层的信息:Minimal String Xoration、月球列车
- 使用倍增法的重要步骤是结合单次询问分析,找到关键的固定的可加速过程,然后倍增:麻烦的杂货店、Hopping Around the Array
- 倍增是基于二进制的,所以某些异或问题用倍增有奇效:温故而知新
- 确定唯一的后继就可以倍增,可以认为地让这个后继满足一些优良性质:唱诗
3.2.5 神奇复杂度
- 只保留有用的点 /// 点对再暴力计算,可以从一些基本的不等关系入手:Weighted Increasing Subsequences、法阵
- 动态的扩展状态,只考虑用得到的状态,在总状态数特别少的情况下特别有用:A Serious Referee
bitset
,一个开挂般的存在,没有减少任何计算量,但是却常常是有效的优化。字符串匹配问题常常可以套用bitset
:Substrings in a String、strings;利用bitset
来递推:New Year and the Tricolore Recreation;利用bitset
加速对应位置异或:区间距离- 找到某个能让值域翻倍///折半的关键元素,可以优化暴力的复杂度:大套子、Phoenix and Diamonds
- 四毛子思想。直接对原序列分块,对于每一块内处理出所有子集的信息,可以平衡复杂度:strings
3.2.6 根号相关
- 总和一定时,注意种类 n−−√n\sqrt n 的结论:Snowy Mountain、Tree Degree Subset Sum
- 值域分治。在位运算和四则运算混合时,预处理时先最大化前一半的数位,再最大化后一半的数位,预处理和询问平衡做到 O(nn−−√)O(nn)O(n\sqrt n) 之类的复杂度:In a Trap;暴力前一半,状压后一半,可以做到 O(n−−√)O(n)O(\sqrt n):进制转换;值域分块带有天然的偏序关系,在偏序关系限制比较严格的时候可以尝试使用:Set Merging
- 根号分治。把种类按照出现次数分类已经是老生常谈了,但是注意分治后的情况下具有的性质:众数、Huffman Coding on Segment;序列跳跃问题可以直接对后继的距离根号分治:Summer Oenothera Exhibition
- 操作分块。结构变化且复杂的题目可以考虑每 O(n−−√)O(n)O(\sqrt n) 个操作分成一块,分别处理。注意这样转化之后结构会变简单,可以把一些复杂的操作用暴力来实现:Jumping Through the Array
3.2.7 贡献法
贡献法最基础的用法就是改变和式的计算方式,使用贡献法时,首先确定贡献对象,然后思考这个对象在什么情况下会产生多大的贡献:Move by Prime
- 多种情况下要统一贡献形式,可能需要钦定合适的贡献方式:钥匙(可以贪心匹配上的就贡献)
- 基于大小关系比较的题目中,可以考虑使用
01-principle
,即先考虑 010101 序列的情况,再用贡献法推广:线段树
3.2.8 随机化
- 扩大值域以减小出错概率。如果想通过行列式表示积和式的一些性质(比如是否为 000),那么可以扩大值域,直接计算行列式,出错的概率是极小的:亿些整理
- 在出现频率差距较大时,随机抽样调查若干次可以获得想要的结果:Olha and Igor
3.2.9 枚举法
- 适当的枚举有助于考虑限制,比如点不交的问题中,可以枚举不交的那个点:Path
- 用枚举法向简单问题化归:Prefix Suffix Addition、制作菜品
- 切换枚举对象,前提是分析清楚枚举过程:鸽子固定器
3.2.10 贪心法
-
我们使用贪心时,尽可能要单一化贪心的对象,独立化贪心的代价。这需要我们观察代价的特点,使用拆分法对代价进行一些处理:[省选联考 2022] 序列变换;贪心对象多化一的处理:新年的腮雷
-
统一代价的形式,这样更便于贪心的使用:Parametric MST;统一操作的形式,也便于贪心:Game Relics、Escape
-
如果代价具有凸性,那么可以会指向堆贪心之类的做法:WYR-Leveling Ground
3.3 数据结构
这里并不是对数据结构的系统总结,而是总结了一些有意思的思维点,可能对你做数据结构有帮助,但是前提还是有扎实的数据结构基础,能对任何结构信手拈来。
3.3.1 问题转化
- 写出操作的线性变换形式,就可以直接套用矩阵:密码箱
- 删除操作,可以看成回退到上一时刻,可以用保留所有历史版本的主席树来支持回退:火车管理
- 对一段区间内的分段函数求和,可以对把自变量当成版本,以位置来建立主席树:Tower Defense
- 对于强制在线的问题,可以先思考离线怎么处理,然后套上可持久化数据结构:区间第k小
3.3.2 考虑限制
- 切换限制的主体。很多时候从题目的方向直接翻译是行不通的,这时候弄清限制涉及到了哪些对象,然后通过选取其他对象的形式来重新表述限制,就达到了切换限制主体的效果:铃原露露
- 保留有效限制。限制之间也存在偏序关系,如果通过一些技巧可以达到排除无效限制的效果,那么就可能把限制的总量规约到一个较小的数量集。比如树上启发式合并来考虑和 lcalca\tt lca 有关的限制:铃原露露、事情的相似度
- 放宽限制。并不一定要在满足限制时检查,可以找维护一个合适的必要条件,把总检查次数限制在一定范围内:被创与地震
- 收紧限制。对于限制较弱的问题,可以通过讨论特殊情况,使得要考虑的范围缩小:Path
3.3.3 确定维护对象
- 尽量不要去维护有关特定值的信息,可以通过放宽条件的方式转化为维护最值:Bear and Bad Powers of 42、Into Blocks
- 可以通过切换主体的方式来确定维护对象。譬如有 A→BA→BA\rightarrow B 的影响,从 AAA 的角度看是一种效果,从 BBB 的角度看又是另一种效果。根据修改与询问的特性变换角度,可以确定最为合适的维护对象:The Tree;先弄清维护的信息是什么,然后切换主体,保持维护信息的不变即可:前进四、诡异操作
- 切换承载信息的主体,前提是原来的主体和新选取的主体之间可以建立对应关系:Nauuo and ODT;寻找决定性的对象,比如这个对象决定了答案,那么我们就尽力去描述它:区间和
3.3.4 思考如何维护
-
如果难以对维护对象选取主元,那么考虑对称的维护两个对象:图论
-
整体标记法。思考清楚整体标记对于单点的影响即可:Latin Square
-
注意维护信息的顺序,比如有的问题只适合以一种顺序加入:Julia the snail
3.3.5 常见模型
- 递归半边模型。其本质是离线与在线的结合,利用维护好的信息每次可以将考虑的区间折半。维护括号匹配:Nastya and CBS;维护极长上升子序列类的信息:转盘
- 染色模型。一些类区间赋值类操作可以向染色模型转化:轻重边;区间求并的问题,可以扫描线加染色模型,也就是维护最晚被染的颜色:rrusq、区间本质不同子串个数
- 猫树分治。主要作用是提供一个分治中点,比如可以把分治中点当成历史最大值的起点:rprmq1
- 二进制分组。可以支持末尾的在线加入,搬到线段树上可以支持末尾删除和区间查询:Unknown