碎碎念
平面图一定要想到“连续”的性质。不可能存在 4 个依序的点 a,b,c,d,满足 a,c 连通,b,d 连通,但 a,b,c,d 不全连通。qwq qaq
二进制具有独立性。如果不完全独立,就把不完全独立的部分塞到状态里,独立的部分记录在 dp 值中。\(\sum k_i2^i\) 只有在高 \(\max k\) 位是不独立的。qwq
\(\color{red}\dagger\) 树上点 \(u\) 的 \(d\)-邻域和 \(fa_u\) 的 \(d\)-邻域具有极强的相关性。\(\delta(u,d-1)\subseteq \delta(fa_u,d)\subseteq \delta(u,d+1)\),\(\left|\mathrm{dis}(u,x)-\mathrm{dis}(fa_u,x)\right|\le 1\)。这个技巧可以用来省去二分。qwq qaq
当题目中出现 \(\times k\) 或者 \(\lfloor {*\over k}\rfloor\) 这样的字眼的时候,且 \(k\) 是全局常数,可以考虑把问题缩小到 \([0,k)\) 的值域内进行考虑;此时 \(\lfloor {*\over k}\rfloor\) 至多改变一次,可以利用好这个性质。qwq
有时候我们会对 dp 出来的结果再进行一遍容斥处理,不要忘了可以改为在 dp 的过程中进行这个容斥。qnq
\(\color{red}\dagger\) 对于费用流的题目,要想办法找到“性质相同则产生额外代价(或收益减少,等价)”的关键部位进行处理,利用费用流的凸性。qwq
网格图上网络流的题目一般都是 \(S\) 连白点,黑点连 \(T\)。
对于建出图跑差分约束的题目,如果直接建图边数很多,不妨考虑一下是怎么连出这么多边的,可以改为跑 \(n\) 轮收缩,每轮内用数据结构维护这些边的贡献。每轮内以任意顺序跑每类边都是对的。qwq
\(\color{blue}\dagger\) 可以使用异或哈希的技巧方便地将点分成若干类,避免压栈弹栈的各种讨论。这里在求边三时用到。qwq
对于所有情况的最大值求和,要记得想办法转化成若干偏序关系,转为在所有偏序下求和。qwq
\(\color{blue}\dagger\) 可以用 bitset
支持 _Find_First
的性质找到满足条件的点中值最小的,用预处理时间前缀的 bitset
来截取一段合法区间。qwq
\(\color{red}\dagger\) 平面图还应该想到欧拉定理。我们都不喜欢算面,但是我们都喜欢算点和边。算是一种广义的点减边。qwq qnq
“跳一跳”类型的题目,除了要想到值域倍增,还要想到位置倍增,例如第一次跳出某个线段树节点,第一次跳过某个猫树中点。qnq
区间 dp 的问题,可以是大区间找小区间,也可以是小区间找大区间。如果大区间要从若干个相同的小区间转移过来的话,那么一定采用小找大的方式,因为同一个东西重复比较好刻画,同时有机会祭出调和级数。
值域倍增不仅可以用在大鱼吃小鱼上,它的本质是进行一个一维的递推,但是每步递推依赖于上一步的结果而不独立;此时可以考虑什么情况下是独立的,不独立的情况是否会导致值域倍增。
关于前 \(k\) 大的值的问题,记得可以扫值域,维护 \(\ge\) 当前分界线的数有几个,这样最大的好处在于(可能可以)不用记录当前已经加了几次。
回忆 \(O(k^2\log n)\) 求线性递推 \(f_n=\sum_{i=1}^k a_if_{n-1}\) 的远处结果:表述成多项式取模的形式,求 \(x^n\) 对一个 \(k\) 次多项式取模,可以快速幂,每次得到 \(2k\) 次的多项式后暴力取模即可,暴力取模就是直接从高位往低位卷回来。
对于要进行若干次矩乘的问题,把它视为在一张图上从 \(x\) 走到 \(y\) 的路径数量,那么我们可以想办法把这若干次矩乘给塞到同一张图里面,这样就可以只做一次了。核心思路是在于我们做一遍矩乘其实是很浪费的,我们只想要 \(x\) 到 \(y\) 的路径数,却求出了图中所有点两两之间的路径数,要想办法把它们利用起来。
在异或和相关的问题中,不要忘了最基本的“如果前 \(n-1\) 个集合已知,那么最后一个集合是唯一确定的”!
数位 dp 可能需要考虑进位,此时记住加同一个数时,低位越大越容易进位,可以按低位排序。
如果要求树/序列上将值域和距离异或起来的最大值,那么只有一种解释,那就是分块。
记住高低位分治也是一种分块。有时候对一个数位 dp 朴素地记忆化即可,理由是高 \(\log/2\) 位只会分叉出根号种,低 \(\log/2\) 位记忆化后显然只有根号种。(本质上可以认为是 最高位向下 和 最低位向上 的蜜桃猫?)
有时候颜色段均摊可以不用 set 刻画而是用线段树刻画,直接线段树上暴力,递归边界为子树内的性质全部相同,会比 set 维护区间好写很多!
记住对于 \(n\) 个体积值域 \(m\) 的物品要求体积和是一个定值的证明方法:当 \(n\) 是偶数时,存在一种方法把 \(n\) 分成两份 \(n/2\),使得两份之间体积差不超过 \(m\)。所以如果 \(n\) 个和为 \(X\),则前后 \(n/2\) 个的和在区间 \([(X-m)2,(X+m)/2]\) 内,故 \([L,R]\) 递归到 \([(L-m)/2,(R+m)/2]\)。
一个总和为 \(S\) 的区间,记第一个满足前缀和 \(\ge S/2\) 的点是 \(k\),则任意一个总和 \(\ge S/2\) 的子区间一定跨过 \(k\)!在值域减半的题目上很有用!
和反悔贪心有关的题目,最好让每次新加的点都可以对之前每个点进行反悔,这样有利于维护和计算!对应于增量费用流的建模上,就是每次新加的点和之前的所有点都有连边。这样处理的时候就可以不用太考虑我反悔了对其他点的影响了。
很多问题中存在一个“可以视为始终小于等于”的性质,例如两个序列,不断往里加点,可以认为第一个序列的长度始终大于等于第二个序列的长度,否则可以交换。这样的推论有时可以推动其他性质的推出,例如交换更优,但当且仅当这个序列的长度大于等于另一个序列的长度时,此时就可以确定确实是交换更优。
关于点减边的问题,可以考虑不使用点减边来刻画,而是使用“至多存在一个点 \(u\),满足 \(u\) 在集合内并且 \(fa_u\) 不在集合内”来计算有多少个集合。这样表达的好处在于“点-边”的话”点“和”边“多少都在变化,而用前面这个判断的话“点”的条件就被弱化了,只考虑“边”是比较好考虑的。
多多尝试将一些和两个点相关的问题转成两个点分别独立的问题:例如,不存在长为 \(3\) 的有向路径 \(x\rightarrow y\rightarrow z\) 等价于不存在一个点 \(y\) 既有入度又有出度。
树上问题记得考虑各种奇怪的差分。例如子树数颜色,可以每个颜色 +1,每 dfn 序相邻的两个同色点 -1,求子树和。这又何尝不是一种点-边呢?
别忘了“至多相差 1”的技巧。
补图上很多问题是可以考虑根号分治的。对于度数 \(\le \sqrt m\) 的那些点,可以很轻易地和其他点连边;对于度数 \(\ge \sqrt m\) 的那些点,本来就只有根号个。
如何唯一地刻画一个 01 序列 \(b\) 是 01 序列 \(a\) 的子序列:将 \(b\) 中的某个 \(0\) 替换成 \(01\) 或将 \(b\) 中的某个 \(1\) 替换成 \(01\)!
莫队的奇偶排序的时候一定记得要判断是否有 r==O.r
!否则会出现 \(a<b\) 和 \(b<a\) 同时满足的情况!
在任何时候都要想到考虑末状态。把行走的过程记录下来变成一张图。
当可能产生贡献的对象较少的时候,要想到让贡献去找点,而不是让点去找贡献。
Boruvka 算法很适合配合隐式图进行计算,因为每次只需要找到一个连通块连出去的任意一条边,这在隐式图中往往比直接 dfs 要来的容易。
把一个算法的“收缩树”建出来,有利于在修改每个点的时候重新模拟一遍算法流程!
世界名画:《 \(O(n)\) 空 间 线 段 树 合 并 》
多次操作之后统一查询全局答案,可以考虑使用差分等技巧简化维护,因为不用动态查询,很多操作简单了很多。
如果要进行若干次决策,最大化很多个点的 \(\min\),可以考虑每次找到最小的 \(\min\),这样我们必须要找到一个决策去影响它。
虚树和路径有交,可以拆成虚树的根在路径上,或者路径的根在虚树上!
独立地从一个集合中删掉两个数,询问集合中的最大值,可以只维护集合中前三大的值就行了。怎么这都忘了呢。
遇到特别奇怪的问题时,可以考虑暴力枚举所有相关的信息,挖掘出仅有的一点点独立性。一个思路是把那些导致相互的影响变得复杂的那些部分给枚举一下。点名批评。
当我们得到一个隐式 \(\pm 1\) 序列并想求出所有和为 \(0\) 的子段相关的信息时,可以只保留其中 \(O(\min{cnt_{-1},cnt_1})\) 个元素。事实上,考虑把折线图画出来,那么一条边可能有用当且仅当它的前面或后面存在高度与之相同的线,或者它是不满足条件的第一条线。可以逐个跳来找到那些有用的位置。
对于一些“树上有几个点满足条件”的问题,可以考虑这个条件的满足性是否可以被传递,即一个点 \(u\) 满足条件是否能推出它的子树都满足条件。这一思想可以被应用到很多类似的问题上。
折半搜索。
“不降的序列”使用从大往小扫每次把前面的那些数统一往上抬是很好的选择。不用记录最后一个数了,改为记录数的个数,可能另一维也需要记录数的个数呢。
dp 一个过程,状态可能设在走的路线上,也可能只设在关键点处。一边走一边拿东西,可以设状态为走到位置 \(i\),拿着东西 \(j\),状态数 \(O(nm)\),转移 \(O(1)\);也可以设状态为到了位置 \(i\),拿着东西 \(a_i\),状态数 \(O(n)\),转移 \(O(n)\)。前者的优势是好写(?),后者则往往有更大的潜力,如数据结构优化。
求图的所有生成树的边权和:给每条边定一个一次多项式 \(wx+1\),则答案为行列式的一次项系数。暴力乘即可。
有些操作是可以动态决定的,而不是都决定好之后再进行操作的!
树上的期望问题,贡献拆到每个节点上之后,每个节点只用考虑它的祖先的状态!!1(怎么现在还忘这一点)
《品酒大会》技巧:按照 height 倒序枚举后缀数组上相邻两个位置然后合并,是很有效的技巧!(有些贪心并不显然)
不要忘了以两个直径端点为根各跑一遍的技巧!
带有过程性的问题,对着终止状态进行 dp。
二分图,每个左部点 \(i\) 向右部点 \(j\) 连容量为 \(c_{i,j}\) 的边,覆盖所有边所需的最小匹配数 \(X=\max\{S_i,T_j\}\),其中 \(S_i\) 为 \(i\) 的出度和,\(T_j\) 为 \(j\) 的入度和,即取到可能的下界!Tanya is 5,可以通过补一些虚点把度数全部补成 \(X\),必能选出 \(X\) 个完美匹配,扔掉虚点之间的边即得到合法方案。
用类似于单调栈的结构不断弹块合并的技巧。
尝试去掉一些简单的,显然的情况。如果要满足多个限制,但满足限制 \(A_i\) 的时候就满足限制 \(A_j\),那么可以直接删去 \(A_j\)。可能这么一删,有的性质就很显然了。
树上对每个点计算它对其他点的贡献之和的时候,可以点分,考虑跨越重心的点对贡献。
对一张图拎出最小生成树来维护!
最长反链的方案构造:求出最大独立集 \(I\),那么反链中的点 \(u\) 为满足 \(in_u,out_u\in I\) 的点 \(u\)。
用 Floyd 对复杂的走路过程进行预处理。我们可能只关心“能从 \(i\) 走到 \(j\)”(然后只考虑这些边来做其他 dp/网络流),但是 \(i\) 到 \(j\) 的路上可能发生很多事情,所以要 Floyd 预处理这条路径。
用“一个点的出流量=入流量”来刻画题目中奇怪的等式。
一定要对偏序集这个条件有极强的敏感度,它有时会隐晦地出现在题目中:比如倍数关系、子串关系。
找出那些有用的量,可能是前几大。如果要选 \(k\) 个数,一个数只会与其他至多 \(m\) 个数冲突,那么只需要考虑前 \(mk\) 大。
各种奇怪的问题都有可能导出欧拉回路的建模。一些“动态”的问题不好解决,此时如果能将其转化成欧拉回路/路径,就可以只用关心每个点的度数以及图的连通性,转化成静态问题,就好刻画了。
网络流的“拆点”不很支持无向边,要想办法把问题转成有向边来刻画。前面的欧拉回路技巧此时可以派上用场:每个点度数都为偶数,也可以视为每个点入度出度都为 \(deg/2\)。
看到题第一时间想想有没有愚蠢的决策!是不是选出来的对象大小均不超过 \(2,3\)!
对于树上的连通块相关 dp,记得把树拍到 dfn 序列上的技巧!每次可以选择跳过一个子树或者进入这棵子树。要求必须经过根,套一层点分即可。
和“乘积的上限”有关的问题,考虑整除分块根号个的性质。
高复杂度 dp 的时候,为了划分子问题,可以考虑各种维度上的“最后一个”:除了最后变化的时间,还可以考虑最靠右的空位,最大的值,等等,总之也是枚举维度的事。
翻转定义域和值域的本质是这两者之间是单增的,所以算一个等于算另一个。题目会问的比较奇怪,要想到把问题换种方式表述。