zxy的思维技巧
如果觉得这篇博客写得好的话不妨点一个推荐(我怎么沦落到求赞的地步了
0.更新日志
我尽量在本篇博客总结我遇到的所有思维技巧,希望深入理解之后能形成思维网络。
本篇博客的选题很多都是 \(\tt CF\ 3000+\) 的题,具有很高的思维难度而非代码难度。
\(2021/8/10\),把 \(dp\) 开了个头。
\(2021/9/30\),继续更新 \(dp\) 部分。
\(2021/10/8\),继续更新 \(dp\) 部分。
\(2021/10/14\),继续更新 \(dp\) 部分。
\(2021/10/29\),感觉复习慢一点也没关系,一定要尽量自己推出来,懂得完整的逻辑链条然后从中提取我所缺失的精华,这才是复习的目的,继续更新 \(dp\) 部分。
\(2021/11/8\),终于开始复习图论啦,现在看来退役之前肯定更不完了,把这个技巧性强的复习完就满足了吧 \(qwq\)
\(2021/11/15\),每次找判定性条件,一定要找必要条件,我已经因为没有往这方面想受到重创了。
\(2021/11/19\),真他吗退役之前更不完了,\(zxy\) 啊 \(zxy\) 之前暑假定下的不断更目标你忘了吗?
\(2021/12/21\),退役一个月之后回归,以后养成好习惯,新写一道题的题解之后就放上来。
\(2022/2/9\),怎么马上就要省选了啊,省选之前争取基本完工吧,这东西真的就是我的遗产了。
\(2022/3/30\),开始涉足一些以前没有整理过的板块。
\(2022/4/10\),字数上万了,这篇博客称为 \(\rm 3A\) 大作不过分吧?
\(2022/7/19\),正式开始 \(\tt NOI\) 之前为期一个月的复习计划。
\(2022/7/27\),稳步进行复习中,大概在 \(8/6\) 结束第一阶段的复习,在 \(8/10\) 结束第二阶段的复习(整理贪心\(\&\)数据结构)。然后重读这篇博客,找出遗忘的地方再次复习,多推一推思路总是好的。
1 dp
1.1 常规dp的思维过程
1.1.1 问题转化
- 比如你要让所有点被覆盖,那么状态可以设计成覆盖一段前缀,并且中间不允许出现断点:CF1476F
- 序列上的路径问题,可以转化成起点和终点的匹配问题,\(dp\) 匹配的权值,记录匹配的标记就可做:The Karting
- 多过程的题,不妨考虑末状态具有什么性质,直接对末状态进行计算。比如一类期望题中,某一种方案的定义方式最后导出等概率出现,就可以直接对此方案计数了:绿宝石之岛
- 高维的问题,可以通过技巧拆分成不相关的低维问题,比如 \(45\) 度旋转:Jumping sequence
- 尽可能的简化问题也是问题转化的一部分,比如把具有平行关系的点缩在一起:Black, White and Grey Tree
- 计数问题中任何性限制原则上优于存在性限制,可以通过切换限制主体来完成转化:Reunion
- 子序列问题可以通过证明不交转化成区间问题,要向简单的 \(dp\) 模型靠拢:AmShZ Wins a Bet
1.1.2 发掘性质
理清思路真的很重要,拿到题你可以先想想什么东西在什么情况下合法。
- 什么时候需要你去发掘性质?当你发现直接 \(dp\) 需要考虑的情况太多了,你可以手玩找一些最优解需要满足的必要条件,才能让你的 \(dp\) 有的放矢,例题:CF1368H1、Division into Two
- 性质是针对限制而来的,在限制较少的题目中可以去往考虑更少情况的方向猜性质:CF573D
- 在具有强烈过程性的题目可以往结果的方向猜性质:To Make 1、模拟赛2-A、Eternal Average
- 很多时候讨论特殊情况可以得到很好的性质:泳池(讨论 \(0\) 的情况可以得到调和级数的性质)
- 如果限制的主体数量级巨大(比如集合、子序列),那么可以考虑归纳、递归的方法描述限制。并且使用这种方法还有一种好处,就是递归子问题很容易拓展到 \(dp\) 的形式:Density of subarrays
1.1.3 思考决策状态
- 可以先使用枚举法帮助思考我们需要决策的状态,然后用 \(dp\) 加速枚举的过程:Sorting Books;Insertion Sort
- 计数题可以想一想需要知道什么量可以用计数原理快速算方案,然后我们用 \(dp\) 决策这些量即可:一拳超人
- 可以选取一种基状态,其他状态可以由基状态修改而来,这时候尽量把问题改成多个量选\(/\)不选的问题,也尽量把他们的影响独立开来,然后用 \(dp\) 决策这个过程:New Year and Binary Tree Paths
- 如果是决策的最终状态,且有多种方式可以到达同一种最终状态,那么强制只用其中一种方式:Mr. Kitayuta's Gift
1.1.4 选定dp主体
我们知道,\(dp\) 的本质是枚举所有状态,但是这些状态必然有一个载体,所以我对 \(dp\) 主体的理解是他只是一种状态的表示方法,我们用它来描述状态,但是状态是最重要的。
很多时候你会遇到很多奇怪的 \(dp\) 主体,但判断它们的唯一原则就是是否能考虑所有状态,选 \(dp\) 主体的时候容易被思维定式局限,所以思考可以从哪些方面来描述状态是重要的,这里有一些关于选定 \(dp\) 主体的例子:
- CF1474F:选 \(x\) 轴为 \(dp\) 主体是不容易的,但是因为我们通常是按顺序考虑子序列所以很容易陷入这个误区,选 \(y\) 轴为 \(dp\) 主体问题就迎刃而解了,所以对偏序关系更高级的理解是若干个限制,不同的题需要从不同的限制入手。
- 卡农:并不一定以小处为主体,比如这题主体为数不好做,但是主体为集合就可以直接转移了。
- CF1463F:遇到集合最优化问题时,可以考虑在值域上规划,选值域为 \(dp\) 主体。
1.1.5 设计dp状态
- 只保留和代价计算有关的量,比如如果代价只和数量有关,并且如果可以通过数量还原出集合,我们可以把状压的一维改成线性的:CF1188D
- 当状态很大的时候可以考虑通过枚举来把某些量拿出状态里面:Game with Cards
- 当问题前后相互影响时,可以考虑把全局变量定义到局部状态里面:密室逃脱;如果操作是全局的,上面的方法也很好用:Red and Black Tree
- 把和限制紧密贴合的量设计到状态里面,阴间出题人可能会用题目描述来引导你设计非常慢的 \(dp\) 状态,做一些基本的题意转化即可:皮配
- 可以设计相反的状态,比如最小花费步数转化为最大减少步数,可能转化后更贴合题设:Paint
1.1.6 确定dp顺序
听着,所谓 \(dp\),最重要的是顺序。无论是考虑限制的顺序,还是计算贡献的顺序,一定要着重思考,我觉得它和结论题中的必要条件有着相同的地位。
- 治疗计划:如果操作是有时间顺序的话,我们很容易被局限于时间中,另一种思路是考虑操作的主体,考虑主体的关系时把时间的影响考虑进去,所以 \(dp\) 不一定按时间。
- 一拳超人:如果一个东西对另一个东西有单向的影响,那么先处理前面那个有助于考虑影响。
- Kyoya and Train:图问题中,要注意转移顺序,通常这一维时单调的就可以作为顺序维,比如时间。
- 集合选数:\(dp\) 顺序尽量让限制紧凑一些,我们也许可以在独立的前提下篡改顺序。
- Singer House、环:注意 \(dp\) 的顺序和方案的顺序是有区别的。比如对于路径计数问题,如果我们按照序列从左到右 \(/\) 树形自底向上的 \(dp\) 顺序计数,那么达到的效果就是若干条有向链合并的过程。
- MEX counting、遗迹:可能需要根据限制的特性,提前\(/\)延迟确定一些元素的过程。
- The Hanged Man:选取合适的 \(dp\) 顺序,可以减少 \(dp\) 状态。
1.1.7 寻找子问题
- 这部分我觉得可以总结一些核心的思想:寻找子问题最重要的是找状态之间的相似性,所谓相似性的含义就是信息记录在子问题中的一部分的占比,相似性越大你写转移就越容易,这需要你强大的观察能力:stars;要去构造问题之间的相似性,比如染色问题中可以通过钦定不变色来获得相似性:Shrinking Tree
- 寻找子问题要考虑消除后面操作的影响,这样才能保证没有后效性:如果某个元素对于前面的影响是长远的,但又只能考虑一小步转移,那么寻找当前问题的等效子问题,就可以把这个影响传递下去,完成转移:stars;或者可以通过枚举法来消除所谓影响:Lanterns
- 当考虑一小步不能很好的归纳到子问题时,可以定义一个辅助数组来解决这个中介状态:矩阵、统计问题中若当前不能计算代价,可以设计辅助数组把代价存下来:消失的运算符
- 枚举法制造限制来划分子问题,比如顺序匹配问题中可以枚举空位:CF1439D
1.1.8 考虑如何转移
-
转移的大步小步。小步适于考虑转移,但是可能会消耗更多时间;大步常常会很复杂,但是可能起到加速的效果。在大步小步之间切换,才能写出合适的转移:Appleman and a Game
-
转移中存在的限制很烦,在一些计数问题中,对于多个元素的限制可以考虑某些元素任意选,另一些元素为了满足限制而具有确定的选法,计数 \(dp\) 中选择的顺序是十分重要的:卡农
-
如果确定转移顺序之后前后会相互影响,那么在后影响前的时候可以通过枚举来解决这个影响,再归纳到子问题:CF1476F
-
多个对象的决策问题中,通常只考虑最后一个对象的决策:CF1439D
-
正难则反是很重要的技巧,比如在计算
第一次到达的方案数
的题目中,容斥掉的项就是子问题:逃跑 -
转移的时候适当的算错,可能会让转移简洁很多:常见的模型是算错但是一定不会作为转移点 link;还有是算少但是最优解可以被统计到:Gerald and Path
-
复合 \(dp\),设计多个 \(dp\) 状态,互相转移的方法是很值得思考的。其实我感觉它的原理还是分步思想,把一个较为复杂的问题拆分成若干个部分解决,这些部分中可能又蕴含子问题:模拟赛6-A;或者是多个 \(dp\),一个 \(dp\) 用于拆分,一个 \(dp\) 用于解决被弱化过的问题,通常可以得到很好的性质:CF1439D、泳池
1.2 常见dp优化方式
1.2.1 斜率优化
- 有些题很难看出需要用斜率优化,把和状态有关的量当成常数,把只和转移点有关的量当成纵坐标,把交叉项系数当成横坐标,把转移点的某个量当成直线去切即可:CF1067D
- 另一类不常见的斜率优化是,变形出斜率的形式,维护凸包:国王饮水记
1.2.2 决策单调性
定义:对于 \(a<b<c<d\),若 \(f(c)\) 从 \(b\) 转移比 \(a\) 优,那么 \(f(d)\) 从 \(b\) 转移也比 \(a\) 优。
判断式:\(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
- 当 \(dp\) 的取值很少且是分层转移的,可以讨论并用数据结构维护状态:Split
- 图论问题可以讨论掉一些较小的环来降低复杂度:Abandoning Roads
- 讨论法很适合处理环形 \(dp\) 问题。环形 \(dp\) 的关键是:断开哪一条环边(我们想要环边具有怎样的特殊性质);断开环边局部的状态是怎样的(简单讨论可以解决):Sonya Partymaker
1.2.5 等价类
- 在很多图论问题中状压环的时候,如果转移只和环的大小有关,那么可以把相同大小的环看成一个等价类:CF1466H
- 只和数量有关而和顺序无关可以按照数量划分等价类:Mr. Kitayuta's Gift
- 乘积不超过某个数的题目可以考虑通过逆向整除分块来划分等价类:Ridiculous Netizens
- 转移方式相同而且易于计算总方案数,可以把它们划分为等价类:逃跑
1.2.6 数据结构优化
- 对于数列覆盖问题,常有的结论是两个点的覆盖范围要么包含、要么相离,这时候可以选择用单调数据结构维护(因为覆盖范围单调),而不是带 \(\tt log\) 的数据结构:CF1131G
- 发现题目有不可解决的维度时,要敢于使用数据结构。但是此时空间消耗特别重要,注意处理 \(dp\) 的顺序,才能时数据结构的使用简单化,并且减少常数的消耗:购票
1.2.7 解决巨大数据范围
- 离散化是解决较大数据范围的一种方式,如果某一段内转移相同,可以离散化出段然后矩阵加速:CF1474F;或者记录离散化段内的信息转移:划艇;离散化还可以把连续型问题转化为离散型问题:Random Ranking
- 可以找循环节,只要不同的循环节之间没有多的限制就行,然后每个循环内部都取最优解。证明的关键在于没有构成完整循环的那部分,可以考虑调整法证明(具体思路见例题):CF1463F
- 对于最优化的 \(dp\) 问题,可以去找转移点的结论,知道每个转移点转移多少次就可以矩阵加速了:CF1067D
1.2.8 考虑转移路径
- 转移路径可以形象化地表示出来,那么可以把简单的 \(dp\) 转化成计数问题,比如这里二维简单 \(dp\) 转网格图计数:[JLOI2015]骗我呢
- 考虑 \(dp\) 的组合意义可以建出图论模型,快速计算时直接枚举其中的某个量:匹配字符串
1.2.9 费用计算优化
- 如果当前状态不好知道,但你清楚代价的变化规则时,可以费用提前计算:Candles
- 如果代价是全局的,那么可以费用延后计算(但一定要注意有无后效性啊??):Red and Black Tree
1.2.10 分治优化
- 背包问题中如果只有一种元素会特殊,那么可以用分治优化,每次保留本层最优解传递上去即可,复杂度会优化的原因是分治会通过在部分中竞争保留最优解:篮球;
- 只要是对于一段区间只有加入就可以尝试线段树分治,不一定要是序列上的区间,可以是先建立树形结构之后,对于一段 \(\tt dfn\) 区间的修改:序列
- 如果只有一个位置不加入,也可以直接分治优化:Random Ranking
1.2.11 神奇优化
- 当转移代价无法优化时,可以考虑转移点数量的限制,然后快速找到转移点:绯色IOI
- 不支持取 \(\max\) 操作时,可以通过考虑转移点的性质来转贪心,通常是权值单向影响的题目:CF878E
- 如果 \(dp\) 值单调,并且需要支持合并和取最值,那么可以用 \(\tt set\) 维护差分标记:魔法树
- 如果 \(dp\) 代价为正并且只存在于单点上,那么可以考虑成类最短路,每次拿到最小的那个能扩展就扩展,用数据结构维护最容易被扩展的点即可(通常是线段树维护最值):治疗计划
- 整体 \(dp\),如果多个 \(dp\) 转移方式完全相同,那么可以考虑一次 \(dp\) 求出答案,比如序列上可以通过端点初始化和端点统计完成:Extreme Extension、Foreigner、星图;更加灵活的形式是找问题之间的关系:Palindromic Hamiltonian Path;其他例子:Mr. Kitayuta's Gift、山河重整、线段树
1.3 常见dp模型
1.3.1 树形dp
- 树背包真正的复杂度是第一维大小乘上第二维大小,特别是第二维很小的情况:CF1097G
- 建立一些使用于特殊性质的树形结构,并在结构上规划:浅谈笛卡尔树结构的应用
- 树重排规划问题可以考虑转区间 \(dp\),本质上就是枚举根的过程:Tree Tweaking
- 路径规划问题,可以枚举起点,然后使用换根 \(dp\) 来获取最优的起点:Svjetlo
- 合并问题可以从叶子开始考虑:Logical Operations on Tree
- 树上路径的限制问题,可以只保留最深的\(/\)最浅的记为状态转移:命运
- 不支持合并的树形 \(dp\) 问题可以考虑转成 \(\tt dfn\) 序上 \(dp\)(树上依赖背包):Ridiculous Netizens
1.3.2 状压dp
- 图计数问题,通常是考虑导出子图,常用的技巧是正难则反:浅谈状压dp在图计数问题上的应用
- 如果只需要知道大小关系,状压 \(dp\) 也可用于确定序列问题上,需要多消耗 \(O(\)值域\()\) 的复杂度:Random Robots
- 状压套折半搜索具有神奇的复杂度 \(O((1+\sqrt 2)^n)\):Harry The Potter
1.3.3 连续段dp
- 连续段 \(dp\) 可以计算端点产生的贡献,这是和连续段数正相关的:摩天大楼
- 写转移的时候可以用两段之间任意长这个条件,方案数就便于计算了:Phoenix and Computers
1.3.4 背包
- 如果装入的总物品不是很多(\(\sqrt n\) 级别)并且连续,可以考虑柱状图 \(dp\),转移分为增加一个柱子和把所有柱子整体升高:逆序对、山河重整;而且这种 \(dp\) 天然带有顺序,如果你想求前 \(k\) 大的话是特别方便的:绿宝石之岛
- 多重背包如果只需要判定存在性,可以维护还剩下的物品数量,转完全背包:AB tree
- 如果背包支持快速合并(有凸性),那么可以直接用分治优化:妹妹卡组
- 要有一个总体是时间观,很多巧妙的背包优化只能把 \(n\) 变成 \(\sqrt n\),如果需要本质复杂度的下降,那么可以尝试改变维护的东西,降维是常用的技巧:Tree Degree Subset Sum、Jumping sequence,降维的关键就是找独立性之后拆分:皮配
- 有一个物品是特殊的,它的选取方式和其他物品不一样,特殊考虑它就能让问题变简单:Lucky Numbers;少量特殊物品时可以考虑分别计算然后合并背包:皮配
- 很多时候背包会存在(隐藏的)拓扑关系,这时候的结论可能是选了小价值物品就必须选大价值物品:Turtle
- 总容量大,但是物品重量很小的背包,可以按二进制位考虑压缩有效状态数:物品
- 前后缀背包,把这个思想搬到树上就是求出 \(\tt dfn\) 正序背包和 \(\tt dfn\) 倒序背包,然后再合并:苹果树
1.3.5 计数dp
计数 \(dp\) 的原则是:初始有一些基础方案,然后我们逐步添加可以区分出方案的东西(这东西是根据方案不同的定义来的),转移到不同的地方就代表这一步产生了不同的方案:高维游走
- 组合意义的嵌套的重要的方法,也就是我们给我们的代价函数确定一个组合意义,那么 \(dp\) 就需要同时完成确定局面和组合计数的功能,常用技巧是拆分:Pass to Next、Stranger Trees、Crash 的文明世界
- 巧妙的枚举可以达到合并两个子问题方案的目的,比如本题枚举可以合并组合数:CF1097G
- 如果判断合法需要使用 \(dp\),而原问题又是一个计数问题时,可以使用 \(dp\) 套 \(dp\),其关键还是建出 \(\tt DFA\):模拟赛7-A、游园会
1.3.6 杂项
- \(dp\) 维护直线函数:CF889E
- \(\tt slope\ trick\) 优化 \(dp\),主要处理代价带绝对值的规划题目:折线算法;可以维护很多跟斜率有关的操作,比如含有 \(\tt max\) 的函数:Increment Decrement
- \(dp\) 维护容斥系数,在大小关系中的计数题中十分常见:小D与随机、不等关系;一些需要考虑集合的毒瘤题中,暴力指数级容斥出奇迹,再转 \(dp\) 维护即可:猎人杀、百鸽笼
2 图论
2.1 图论问题的思维过程
2.1.1 图论模型的建立
首先考虑对于原问题的什么对象建立模型(主体的选取),然后尝试用图论的各种意义去表示原问题中的元素(比如点权、边权、路径、连通块\(...\)),这两步都不要被定式思维所限制。
以题目中的一个限制为基础建立模型,这一步不要被思维定式所局限。
- 遇到难以处理的全局限制时可以考虑图论:Maximum Adjacent Pairs
- 矩形中类似推箱子的问题,可以把箱子的移动转化成空格的移动,建立关于空格移动路径的图:Shifting Dominoes
- 区间元素和的判定问题可以把前缀和建成点,原图中的元素建成连接前缀和的边:Flip and Reverse
2.1.2 图结构的分析
- 树形结构具有很多优美的性质,可以通过分析度数与环来证明树形结构:Shifting Dominoes
- \(dfs\) 树是很重要的思维方式,通常我们分析树边和非树边能得到很多有意思的性质。如果是有向图还要考虑哪个点为根更好分析:James and the Chase;或者可以通过 \(\tt dfs\) 树直接给出构造:Nezzar and Hidden Permutations;Weighting a Tree
- \(\tt DAG\) 具有很好的性质,所以就算是遇到一般图时,也可以先思考在 \(\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 最短路
- \(\tt dijk\) 的遍历思想在很多题中有大用处,如果每个点只需要遍历一次那么维护最有可能遍历的点:Mike and code of a permutation;治疗计划
- 动态加边可以解决到达时间最小的限制,它的本质是如果具有连通性就可以说明最早到达:Dirty Arkady's Kitchen;动态加边还可以直接维护强连通性,对正反图动态 \(\tt bfs\) 即可:图函数;动态加边还可以完成版本之间的转化,结合 \(\tt spfa\) 可以做到某些情况下的动态最短路:道路堵塞
- 用 \(\tt dijk\) 可以保证一些特殊的转移顺序:Intergalaxy Trips
- 环上的最短路,如果数据范围极大,先考虑重构环之后再扫环:Drazil and His Happy Friends
- 删点后求最短路的问题有固定套路,就是考虑有一条边一定会跨过这个点,可以拼凑出路径来:风之轨迹;道路堵塞
2.2.2 连通性
- 互达问题可以思考连通性,连通性重要的一点是传递性,有些问题可能只有特殊点具有连通性:Cow and Vacation
- \(\tt lct\) 可以维护动态边双连通分量,考虑把非树边的影响转化成赋值标记即可:Bear and Chemistry
- 无向图路径的最值问题,可以将边权排序之后转化为维护连通性(最小生成树只是特例):路径查询
- 对于带修改的问题来说,可以有一个统一的固定结构来处理两点连通的时刻(边起作用的时刻):Gates to Another World;维护强连通性,可以用整体二分求出每条边强连通的时间,然后转化成维护无向图的连通性:WD与地图
2.2.3 拓扑排序
- 拓扑排序可以提供一种解构图的顺序,注意拓扑排序中天然带有的可达性:Pink Floyd;如果需要一些出现顺序的关系,可以先建立图之后考虑其拓扑序:Insider's Information
- 拓扑排序的性质,同在队列里的点没有任何到达关系,可以通过它来筛选合法点:Upgrading Cities
- 拓扑排序的另类形式:按 \(1\sim n\) 的顺序在反图上 \(\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;考虑重心可以去掉一些关于子树大小的 \(\min\) 式子:Distance Matching
- 树上最远点问题可以转直径端点:CF516D、广义的直径问题是带权匹配,是支持点权的:情报中心;最长链问题也可能和直径有联系:模拟赛7-B;以直径中点为根建树可能是不错的选择:Drazil and Morning Exercise;以直径端点建树也可能有很好的性质:Spiders Evil Plan
2.3.2 树上算法
- \(\tt lct\) 有着特别重要的染色模型,也就是用实边代表同色,虚边代表异色,修改就是把一个点到根的路径染色,然后用数据结构维护颜色的方法:Matches Are Not a Child's Play、树点涂色
- 延迟贪心,即能不放置关键点则不放置,这样能把关键点放置在最浅的位置:Squid Game
- 要快速计算虚树的大小时,可以考虑下标为 \(\tt dfn\) 序的线段树来维护:语言;这种用线段树去重的方法还可以应用于字符串中(若干后缀的本质不同前缀,\(\tt sam\) 上启发式合并):Asterisk Substrings;这类线段树合并的本质是用线段树的结构提供了一个合并顺序:三角形
- 最大\(/\)最小边权问题考虑 \(\tt kruskal\) 重构树。比如想要求虚树的最大边权,可以转化成重构树上最浅的祖先,即 \(\tt dfn\) 序最大点和最小点的 \(\tt lca\):Groceries in Meteor Town
- 路径问题考虑点分治,不要被复杂的形式给诈骗了:Network
2.3.3 常见模型
- 连通块问题的常用解决方法:可以在连通块的最浅点统计这个连通块的贡献,那么用树形 \(dp\) 来规划这个最浅点即可:切树游戏、Ridiculous Netizens;还可以用
点数-边数=1
的经典容斥:完美的集合;选取连通块的代表点时,可以套用点分树这种分治结构:成都七中、Ridiculous Netizens - 还原树的问题中,可以分步还原,也就是先还原特殊点的结构,再还原整体的结构:Restoring Map;New Year and Forgotten Tree
- 边权和贡献最大问题可以往长链剖分这个角度考虑,如果是路径问题可以通过 \(2k\) 个叶子的构造性结论转化成最长链问题(前提是需要定根):Spiders Evil Plan
- 巧妙利用树上结构,如可以用重链剖分的结构来快速定位:Nauuo and Binary Tree;若是多个数通过操作合并为一个数,可以思考操作的树形结构:Eternal Average;括号序列的树形结构是重要的:[省选联考 2022] 序列变换(差点因为这题没进队)
2.4 网络流
有一些入门的内容,不想整理了:网络流简单题选做;网络流二十四题
把下面这些整理完之后,我才发现好像网络流确实已经很难有新意了,很多 \(\tt trick\) 都是反复出现的。
2.4.1 量的意义
- 用流量的流入和流出代表加减,可以表示一些加减的不等式关系:Red-Blue Graph
- 流量可以在基于要求的情况下,表示解决要求的途径,而费用可以看成途径的代价:Chips Challenge
- 可以用路径来表达不合法的关系,再用最小割来获取最优解:Starry Night Camping;老C的方块
- 对于覆盖类型的限制,把需要覆盖的点串联起来,用路径表示覆盖的关系:奇怪的线段树
2.4.2 观察性质
- 如果用网络流解决平面图问题,可以考虑黑白染色转二分图的性质:过山车
- 网络流解决矩阵问题,可以用行列来建二分图:;但这有时候是个思维定式,如果行和列独立并且有其他限制,那么建单层图能更好地表达限制:Asa's Chess Problem
- 完美匹配的等价条件是 \(\tt Hall\) 定理,所以如果原问题和 \(\tt Hall\) 有某种关联可以通过这层关系转化到网络流上:Construction of a tree
- 拆分法,网络流一般只能解决单点对单点的限制,如果是多点对单点,那么大胆找结论,我们可以从答案的形式入手:Rainbow Triples;还有对代价的拆分,比如绝对值的微元贡献法:One Billion Shades of Grey
- 如果某个问题可以建出其费用流模型,那么是有凸性的,就引申到了 \(\tt wqs\) 之类的算法:April Fools' Problem
2.4.3 小技巧
- 单点的多重意义,可以考虑拆点,通常可以让限制表达得更明晰:过山车,Making It Bipartite
- 动态网络流,如果有多个图但是差别不大,可以把多出来的边退流:模拟赛7-C;One Billion Shades of Grey
- 手算最小割,常常需要枚举法和数据结构维护:Rainbow Triples;也可以对于点所属的颜色来 \(dp\):Breadboard Capacity;手算最大流也常用,使用结论然后数据结构维护:k-Maximum Subsequence Sum
- 保留有用的边,减少边数:Bridge Club;划分等价类,减少点数:小埋与游乐场
- 优化建图,本质和图论的优化建图是同一类方法,\(cdq\) 优化建图:通信;主席树优化建图:a+b Problem
2.4.4 图匹配问题
- 一般图匹配也许可以通过结论转化成二分图匹配(左部右部的匹配次数相同):模拟赛5-A
- 一些情况下贪心地匹配:Add to Square(达到构造的界);模拟赛2-C(特殊的偏序关系)
- 二分图的独立性(只需要考虑某一部的具体情况):Bipartite Blanket
- 不能走回头路的博弈问题可以考察二分图匹配相关的结论,这是因为交错路具有特殊的数量关系:游戏
- 经典问题:有向图求环划分,可以拆成入点和出点跑二分图匹配:边
2.4.5 常见模型
- 混合图的欧拉回路,思考清楚度数在原问题中对应着什么即可套用此模型:wait
- 最大权闭合子图,可以解决带强制选取关系的选\(/\)不选问题,或者也可以解决类似的二选一问题:魔法商店
- 最长反链,转最小链覆盖之后,用 \(\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、神经网络
- 如果存在选取个数 \(\leq a_i\) 的限制,可以强制选取 \(a_i+1\) 个,然后记上 \(-1\) 的容斥系数:钥匙
- \(\tt LGV\) 引理,原本求的是偶数逆序对的不交路径减去奇数逆序对的不交路径。在特殊的图中(比如网格图,数轴)可以直接求不交路径数量:无损加密,如果终点未知还可以考虑 \(dp\) 计算行列式:Random Robots
- 容斥的另一种思考方向,先给出一种会算重的计数方法,然后找出这种计数方法算重的原因,然后对这个算重的原因容斥:树;比如经典模型 \(\tt DAG\) 计数,就是容斥入度为 \(0\) 的点:Finding satisfactory solutions;类似的基环树计数,可以容斥叶子:人类补完计划
- 集合划分容斥,用于解决相等关系的限制,直接对边数容斥即可:Distinct Multiples
3.1.3 统计方法
- 思考答案的形式,然后思考在哪里统计,如果答案是区间则可以在端点处统计:Chords;如果答案是树上的点集,那么可以在其 \(\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
- 斯特林数模型,可以在 \(n\) 大 \(k\) 小的情况下优化计算 \(n^k\):Vladislav and a Great Legend
- 本质不同字符串计数的问题,直接考虑建立 \(\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;可以通过一些简单的操作变换,使得减少量和花费的时间之间对应起来:五彩斑斓的世界 - 势能线段树,可以维护区间取 \(\min\)、区间除法等操作:浅谈势能线段树在特殊区间问题上的应用、Cartesian Tree、Stations
- 均摊法最重要的是分析总体复杂度,所以一些暴力的操作可能看起来很慢但是总体复杂度优秀:Berland Miners
3.2.2 拆分法
使用拆分法时,最重要的是保证独立性。
- 如果题目中存在时间顺序,可以把这个顺序破除来保证独立性(即换一个顺序计算):Addition and Andition
- 可以对左右端点拆分来优化,比如应用到区间 \(dp\) 问题: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 根号相关
- 总和一定时,注意种类 \(\sqrt n\) 的结论:Snowy Mountain、Tree Degree Subset Sum
- 值域分治。在位运算和四则运算混合时,预处理时先最大化前一半的数位,再最大化后一半的数位,预处理和询问平衡做到 \(O(n\sqrt n)\) 之类的复杂度:In a Trap;暴力前一半,状压后一半,可以做到 \(O(\sqrt n)\):进制转换;值域分块带有天然的偏序关系,在偏序关系限制比较严格的时候可以尝试使用:Set Merging
- 根号分治。把种类按照出现次数分类已经是老生常谈了,但是注意分治后的情况下具有的性质:众数、Huffman Coding on Segment;序列跳跃问题可以直接对后继的距离根号分治:Summer Oenothera Exhibition
- 操作分块。结构变化且复杂的题目可以考虑每 \(O(\sqrt n)\) 个操作分成一块,分别处理。注意这样转化之后结构会变简单,可以把一些复杂的操作用暴力来实现:Jumping Through the Array
3.2.7 贡献法
贡献法最基础的用法就是改变和式的计算方式,使用贡献法时,首先确定贡献对象,然后思考这个对象在什么情况下会产生多大的贡献:Move by Prime
- 多种情况下要统一贡献形式,可能需要钦定合适的贡献方式:钥匙(可以贪心匹配上的就贡献)
- 基于大小关系比较的题目中,可以考虑使用
01-principle
,即先考虑 \(01\) 序列的情况,再用贡献法推广:线段树
3.2.8 随机化
- 扩大值域以减小出错概率。如果想通过行列式表示积和式的一些性质(比如是否为 \(0\)),那么可以扩大值域,直接计算行列式,出错的概率是极小的:亿些整理
- 在出现频率差距较大时,随机抽样调查若干次可以获得想要的结果: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 考虑限制
- 切换限制的主体。很多时候从题目的方向直接翻译是行不通的,这时候弄清限制涉及到了哪些对象,然后通过选取其他对象的形式来重新表述限制,就达到了切换限制主体的效果:铃原露露
- 保留有效限制。限制之间也存在偏序关系,如果通过一些技巧可以达到排除无效限制的效果,那么就可能把限制的总量规约到一个较小的数量集。比如树上启发式合并来考虑和 \(\tt lca\) 有关的限制:铃原露露、事情的相似度
- 放宽限制。并不一定要在满足限制时检查,可以找维护一个合适的必要条件,把总检查次数限制在一定范围内:被创与地震
- 收紧限制。对于限制较弱的问题,可以通过讨论特殊情况,使得要考虑的范围缩小:Path
3.3.3 确定维护对象
- 尽量不要去维护有关特定值的信息,可以通过放宽条件的方式转化为维护最值:Bear and Bad Powers of 42、Into Blocks
- 可以通过切换主体的方式来确定维护对象。譬如有 \(A\rightarrow B\) 的影响,从 \(A\) 的角度看是一种效果,从 \(B\) 的角度看又是另一种效果。根据修改与询问的特性变换角度,可以确定最为合适的维护对象:The Tree;先弄清维护的信息是什么,然后切换主体,保持维护信息的不变即可:前进四、诡异操作
- 切换承载信息的主体,前提是原来的主体和新选取的主体之间可以建立对应关系:Nauuo and ODT;寻找决定性的对象,比如这个对象决定了答案,那么我们就尽力去描述它:区间和
3.3.4 思考如何维护
-
如果难以对维护对象选取主元,那么考虑对称的维护两个对象:图论
-
整体标记法。思考清楚整体标记对于单点的影响即可:Latin Square
-
注意维护信息的顺序,比如有的问题只适合以一种顺序加入:Julia the snail
3.3.5 常见模型
- 递归半边模型。其本质是离线与在线的结合,利用维护好的信息每次可以将考虑的区间折半。维护括号匹配:Nastya and CBS;维护极长上升子序列类的信息:转盘
- 染色模型。一些类区间赋值类操作可以向染色模型转化:轻重边;区间求并的问题,可以扫描线加染色模型,也就是维护最晚被染的颜色:rrusq、区间本质不同子串个数
- 猫树分治。主要作用是提供一个分治中点,比如可以把分治中点当成历史最大值的起点:rprmq1
- 二进制分组。可以支持末尾的在线加入,搬到线段树上可以支持末尾删除和区间查询:Unknown