trick
普适做题思路总结
- 先尝试简化版,再扩展至复杂版
- UVA1437 string painter 先考虑 空串 $\to $ B串,再计算出上述结果后在此基础上,解决A串 $\to $ B串。
- Acwing146. 序列 多个序列操作化简为两个,最后数学归纳法证明 \(n\) 个成立,并且可以通过两两处理,始终保证为两个序列的操作。
-
找到变量与不变量,大胆列式子,注意可能抵消之类的。
-
反向考虑贡献
\(A_i\) 有多少个 \(B_i\) ,可以考虑每个 \(B_i\) 对哪些 \(A_i\) 有贡献,而非从直接从 \(A_i\) 角度考虑。或者说 \(A_i\) 有属性 \(B_i\) ,有多少个 \(A_i\) 和 \(A_j\) 有相同属性,可以考虑每个 \(B_i\) ,有哪些 \(A_i\) ,直接扫描 \(A_i\) 丢入 \(B_i\) 桶中。
其中一种处理方法是离线,分类/排序后一个一个加入,比如 P2664 树上游戏 的部分分。 -
遇到无规律贡献或者一些难以使用的东西,可以考虑配对使得贡献有规律。CF468C Hack it! 中甚至配对不是都加,而是一个加同时另一个减。
-
如果猜的结论过于特殊,可以考虑将减少一些结论的特殊性,使得普适化
-
尝试主动从约束条件中推出性质,而非被动去满足约束条件
-
拿到一道题千万不要直接从一个方向开始死钻,应该先多发散思考出多条路,然后再选择可行的。可以考虑正反思考等
-
有的时候看到奇怪约束也不一定要分析性质,直接去满足约束即可。P8251 [NOI Online 2022 提高组] 丹钓战 不要想复杂了,分析过多的性质啥的,直接想清楚本质,无脑倍增即可。注意能弹掉成功的二元组的二元组必然是成功的。
-
遇到不会处理的变量,尝试标准化处理。比如树上两点距离,转深度。
-
不一定从整体思考,可以从局部思考 如:从 \(l\) 异或到 \(r\),可以发现相邻两数异或为 \(1\),然后合并。CF1493E Enormous XOR
-
对于复杂操作,可以思考巧妙转化。
-
结论题打表找规律。CF1493E Enormous XOR
-
对于一个充分条件或者必要条件考虑能否推到充要条件。
-
有的共有的变量不一定要提前处理掉,放在一起处理可能会有特殊性质
-
尝试化简自己的复杂做法。相信标程永远是简洁优美的,复杂做法自己也未必能写出来。
-
可以从局部或者整体双考虑,如果一条路走不通走另一条,比如冒泡排序一次对整体改变若干个逆序对,但对单个元素只改变一个逆序对。
-
先从性质角度感性理解/猜结论,再尝试证明。如 P7962 [NOIP2021] 方差。由方差性质,数字越平均方差越小,当差分呈现单谷的时候,数字最平均。
-
有一些好的结论后,不会写正解就通过结论 暴搜 加剪枝,P7962 [NOIP2021] 方差 ,枚举每种方差在谷两边出现次数。同时可以尝试证明暴搜复杂度,详见题解。
-
二合一题别被诈骗了。P3646 [APIO2015] 巴厘岛的雕塑
-
思考题目顺序:先想暴力,然后想优化。整体和个体双考虑。
-
一定不要对着某个角度死磕,注意换思路,思考一道题目的时候不要只对着一个算法死磕,不会的时候尝试枚举自己学过的所有算法。
-
思考特殊位置的移动,比如本题的约束是 \(>\),那么我们就思考前 \(k\) 个数中最大值的移动,发现很显然就出来了。然后是次大值,以此类推。
序列操作/数据结构
-
倒序操作
若后续操作覆盖先前操作,可考虑倒序,直接看对最终答案有影响的操作。 有时也可以通过排序在不改变结果的前提下改变操作顺序,使得后续操作可以完全覆盖前面操作。CF217E Alien DNA -
多个集合维护可以考虑根号分治。
-
单点修改用分块,可以维护块间信息。
-
哪吒闹海「多校联考2019」 可以考虑操作问题方法上的等价转换。转化为维护连通性。
-
P5283 [十二省联考 2019] 异或粽子 对 \(i\) 的第 \(k\) 大推导到 \(k+1\) 大我的思路是从第 \(k\) 大的点出发回退找到另一条路。其实可以通过子树大小决定往左/右走。
-
树套树,如果是树状数组套动态开点线段树的话,注意线段树那一维度如果要支持区间加,单点求和的话可以用差分,这样子就不用维护标记了。
图论
-
有约束关系的考虑图论连边建模。
其实不止是有关系的点之间连边,还可以将边赋予特殊意义。
选不选可以考虑最小割。
题型总结:一般为一种有两个选择的题目,可以将两种选择对应的结果设为两点,然后将需要作出选择的东西当成一条边连起来。P8095 [USACO22JAN] Cereal 2 S 可以将第一二喜欢的作为点,将所对应的奶牛作为边。
CF1519E Off by One 将向两个方向平移的结果当成两个点用一条边连起来。 -
树上问题要有局部意识,比如以子树为单位,进入子树前后差分,或者动态修改统计信息。P5838 [USACO19DEC] Milk Visits G
-
树上问题先考虑链。
-
树上问题解决思路:其实就是像分治一样,每个子树的处理方法相同,然后到父亲的时候所有儿子已经满足条件。
动态规划类:其实就是把子树独立考虑和整体考虑,设 \(f_{i,0/1}\) 表示是否和父亲产生联系。这样就可以覆盖所有情况,\(0\) 的时候代表以该点为根,\(1\) 的时候表示需要考虑上面再接一个点。CF1332F Independent Set
其他:CF1521D Nastia Plays with a Tree 不要想太复杂还是以子树为单位考虑,一定要想清楚:其实到父亲的时候儿子子树已经全都是链了!!只需要对于链贪心连或断就行了。 -
[CSP-S2019] 树的重心 这题中题目给了重心性质的提示,使得问题可以转化。但要是没有给提示,也要想到最大子树最小其实很难快速动态维护与比较,这时候可以利用性质将重心等效转化为最大子树大小小于原树大小一半,这样就方便统计与维护了。
-
满足可减性的树上线段树合并之类的,也可以 dfs 序主席树。
-
CF1310D Tourism 大图中实际经过的点很少很少的时候可以随机赋一个阶段,然后多跑几遍。
-
当思考的时候想到一种类似链式搜索(就像一环套一环从这里引出另一个)的方法,可以考虑建图。例:P8095 [USACO22JAN] Cereal 2 S。
-
补图很小,可以考虑用 set 维护转移集合。进行 bfs,首先已访问过的点必须从 set 中删除,每次取出一个节点将与它没连边的点从 set 删除,每次从 \(u\) 选择 \(set_u\) 集合内部的点进行转移即可。
判断联通性,就是找到一个补图中度数最大的点,然后标记一下周围点,再到补图中与其相邻点标记一圈。 -
最小生成树可以转化为联通性相关问题。CF1242B 0-1 MST 0-1边权,直接算 \(0\) 联通块个数即可。
-
联通块个数为 \(\lvert V\rvert-\lvert E\rvert\)。
-
数据范围为几百的贪心题/匹配状物题,考虑网络流。
-
树上问题不一定要从根开始 dfs 然后统计计算。可以从某点出发,不断向着可能产生答案的地方靠近。
P4886 快递员 向着可能缩小答案的子树逼近。
P5666 [CSP-S2019] 树的重心 不断向着可能产生重心的最大子树。 -
树上问题不一定要从根开始 dfs 然后统计计算。可以从某点出发,不断向着可能产生答案的地方靠近。
P4886 快递员 向着可能缩小答案的子树逼近。
P5666 [CSP-S2019] 树的重心 不断向着可能产生重心的最大子树。
动态规划
-
动态规划时若前面为一类后面为一类的话,不需要暴力枚举断点,只需前后各dp一遍然后拼接即可。
-
bool 型 dp 解决思路 :bitset/提取状态到 dp 值中。
-
动态规划的时候如果对后面有影响的话,可以开一维记录后面需要补充几个。
-
一次函数型贡献,凸包斜率优化/李超线段树。
-
遇到区间加别急着树状数组,记得前缀和和差分,尤其是 \(O(n^3)\) 暴力的时候,变成 \(O(n^2)\) 后允许每次都统计一遍前缀和和差分,如果 \(O(n^2) \to O(n)\) 就不行了。
-
P3177 [HAOI2015] 树上染色 树上背包一定卡死上下界。
-
UVA1347 旅行 Tour 将归程改为反向出发,于是就变成从起点到终点找两条路径。设 \(dp(i,j)\) 表达 \(1 \to \max(i,j)\) 都走过了的最短路径,不妨强制 \(i>j\),然后再强制一下转移,下一步必须走到 \(i+1\)。正确性验证:如果 \(i \to i+2\) 后显然无法覆盖 \(i+1\)这个位置,那么必须要 \(j\) 来覆盖,那我们就可以改变一下顺序,让 \(j\) 先到 \(i+1\),再让 \(i\) 到 \(i+2\),这是等价的。
-
P1437 [HNOI2004] 敲砖块 不会做的 dp 考虑,改变转移顺序,改变目标,改变贡献方式。
贪心
-
对于一个很容易想到,但是错误的贪心进行微调,说不定就是正确的了。
-
序列上进行最优或者可行性的贪心的时候,不会的话就可以考虑根据最大最小值划分。
CF1503D Flip the Cards P9870 [NOIP2023] 双序列拓展 -
多重约束,干扰项太多很难正常贪心的时候就要放弃贪心思路,转战 dp/网络流来解题。
-
当我们采用交换法的时候,一定要记住是邻项,然后大部分情况就不用考虑对于后面的影响了,只需要考虑两个之间的影响。
数学
-
看到贡献式子中有 $ x \bmod p$,不会算就转化为 \(x-\lfloor\dfrac{x}{p}\rfloor\times p\)。
-
可以将题目转化为对应的数学模型,化简技巧:可以将相同变量放在一边。
-
\(\lfloor\dfrac{\lfloor\dfrac{t}{x}\rfloor}{y}\rfloor=\lfloor\dfrac{t}{xy}\rfloor\),应用:一个数不断除以某数向下取整,可以合并这个过程。
-
数形结合。CF1513F Swapping Problem 本题同时需要适当分类讨论,从局部来考虑两条线段的相交分离情况。
-
在 \([0,1)\) 上随机选 \(n-1\) 个点,第 \(k\) 短的线段的期望长度为 \(\dfrac{1}{n}(\prod\limits_{i=n-k+1}^n\dfrac{1}{i})\) 证明可用微积分P6130 随机红包。
杂项
-
如果题目给出若干个集合,就尝试对他们进行取并,查看包含关系等。CF566E Restoring Map 对于邻域信息取并得到连边关系,查看包含找到叶子。
-
剪枝或者卡常可以特判加速
甚至可以在一些奇怪的地方(比如发现一个循环实际有效操作很少,可以考虑判断一下明显不可能的操作)
P5309 [Ynoi2011] 初始化 判断如果没有 \(pre[i][i]\) 就不对它进行操作。 -
如果一个变量可能会被极端数据卡掉,考虑它的反面。
保证每个变量至多被操作一次,这样就可以均摊复杂度了。过程比较抽象不太好描述。
P6282 [USACO20OPEN] Cereal S 正着做看似均摊可以,实则会被极端数据卡没,于是可以倒序操作计算。 -
若枚举量过大,考虑独立性,可以分别取最优解或者分别计算。CF321D Ciel and Flipboard
-
子集求和
sum[s]=sum[s-lowbit(s)]+sum[lowbit(s)];
-
如果允许 \(O(n^2)\) 且是多维的不想记录 id,可以用冒泡排序。
-
可以考虑牺牲常数复杂度以此化简分类讨论难度,比如 CSP-S 2022假期计划,直接用 \(set\) 记录前三大枚举匹配,而非分类讨论如果第一大重复,第二大重复之类的繁琐问题,这样做法虽然增加了 \(3\times 3\) 大的常数,但是方便了不少。
-
有的题目看起来操作很复杂,甚至连最基本的思路都没有。但其实实际涉及到的基本操作很简单,如 喵了个喵 乍一看为一堆对应很多种牌乱搞,实则仔细思考后为两种牌对应一个栈分开处理,同时观察数据范围发现两倍关系可以引导我们想到这个结论。
-
贪心的时候一定考虑到决策包容性,看似有一些小 bug 的错误贪心,其实就是正解,只不过通过替换或者等效等手段可以证明小 bug 不会发生。
-
常数优化:枚举时排除不可能状态,比如 \(dp\) 不可能状态就不枚举了,使得枚举量 \(n\) 降到 \(n/2\),如果二维枚举,常数就变成了 \(1/4\) ,可以用于卡常。
-
选取恰当的周期/阶段。
CF1340C Nastya and Unexpected Guest
题目要求红灯行,绿灯停,且红灯时必须在一个安全岛上。所以可以将一个绿灯 \(+\) 红灯视为一个周期/阶段。0/1bfs 即可。
P3147 [USACO16OPEN] 262144 P 选取当前合成的数字为阶段,往上转移 -
善用 c++ 自带函数。
ARC160A Reverse and Count 求第 \(k\) 大的序列,直接用 \(nth\)_\(element\) 然后自定义大小比较
-
一种很有用的思想,就是如果一个东西的时间复杂度会随着操作次数变多变得不正确,那么我们就加入一个反弹机制,使得他越操作反而可以帮助我们减少时间消耗。很抽象,不太好描述,有一个例子比如启发式分裂,他的分裂点越偏本来是让我们花越多时间的,但是我们两边扫可以反而更快。
-
善用倍增。CF1707E Replace,本题两个倍增提供了两种使用倍增的思路,区间信息具有可并性的时候可以倍增,迭代也可以用倍增。
-
当我们想减去一个不可减的东西。可以维护一个双栈结构。例子 \(1\) 在我动态规划文章双栈优化 dp 里。例子 \(2\) 是【UR #23】地铁规划。
-
P8431 「WHOI-2」彗星蜜月, 如果满足条件的为一个区间,那么求最大满足条件的可以转化为求最小不满足条件的。
-
区间最大值约束,可以考虑笛卡尔树,类似一个分治结构,如果可以带 \(\log\),可以用 rmq + 分治实现。这里的T2。
-
满足条件的区间产生贡献,可以用双指针。CF372D Choosing Subtree is Fun
-
操作简单,但是难以维护的判断考虑用哈希维护。CF1746F Kazaee
-
简洁转化:AGC012C Tautonym Puzzle \(\to\) P8376 [APIO2022] 排列
-
数区间颜色个数可以用 vector 记录出现位置,然后二分左右端点处理。
-
如果统计最长区间,出现大满足那么小必然满足,也就说出 \([l,r]\) 满足条件,那么 \([l+1,r]\) 必然满足条件类似的性质,可以考虑双指针。
-
动态计算的时候需要满足题目约束,但只要加入的东西在后续最优情况下符合即合法,我们只要记录约束条件在后面继续判断即可。
-
考虑旋转坐标系。ABC221G Jumping sequence 本题本来 \(x,y\) 是分别操作的,每次只影响一个维度,旋转之后每次影响两个维度。这样子两个维度就独立了,可以对于每个维度依次执行那几个操作。
-
对二维信息的处理尝试转化到平面方格图上。比如二元组。
-
如果发现某东西的数量为 \(n!\),尝试与排列一一对应。
-
最优化一个序列,可以考虑先填入不重要的。然后再找准时机填入重要的。
P3243 [HNOI2015] 菜肴制作 正着考虑字典序最小是错解,因为无法保证 \(1\) 更早到达,正确的应该是走到 \(1\) 的最短路径中最优的一条。会发现要求非常 "紧迫",因为要求以最快速度填入 \(1\),然后是 \(2\) 以此类推。同时满足先到 \(1\) 和路径更优这两个条件,这样操作空间不大。但是如果我们建反图考虑的话,发现反正是尽可能后的放 \(1\) 所以并不着急放 \(1\), 可以随时放。这就从容许多。于是因为没有“先到 \(1\) ” 这个任务,那就可以直接字典序了。 -
可以动态依次加入可以对答案产生贡献的点。CF459E Pashmak and Graph
-
如果给出的是集合而非序列是否有更好的方法,从非序列角度考虑。
-
如果给出最终态需要至少满足什么条件,那就逆推。UVA10537 The Toll! Revisited
-
异或也可以差分,P5057 [CQOI2006] 简单题
-
CF1091F New Year and the Mallard Expedition 别想太多,直接贪心,模拟完补上贡献即可。
-
CF1398F Controversial Rounds 发现答案的级别是调和级数,于是只要时间复杂度与答案同阶即可,我们可以直接暴力跳。
-
没有单调性的二分,可以创造单调性,比如这题中间需要处理最长前缀 \(0\),不可直接二分,但可以转化为前缀和,然后树状数组倍增。P10308 「Cfz Round 2」Osmanthus
-
最优点对,可以枚举一个,数据结构另一个,或者整体考虑。
-
排列问题可以考虑逆置换。AGC001F Wide Swap 发现对于值域的要求异常严格,对于位置的要求宽松。对于我们来说位置关系其实更直观,可以考虑逆置换,将值域要求转化到位置上去,位置要求换到值域上来。然后像这种交换或者移动可达性问题直接猜测满足必要条件即可。
-
如果出现需要每个数都二分一下,可以考虑能不能用双指针来做,去掉 \(\log\)。
构造/交互
-
一般构造题各种操作的可能性太多了,不能枚举构造,我们可以根据题目给的要求,缩紧约束,这样子才能构造。
-
最小化构造可以尝试思考上下界能否达到。
-
尝试从终态倒着构造。
-
构造题注意可以提前算出需要构造的形态,然后构造。避免了一边算一边造的动态调整的麻烦。
-
构造题从考场大小样例中猜做法。
-
带有最优化的构造题可考虑用 dp 优化。CF1521D Nastia Plays with a Tree
-
交互题,类似分治/搜索的时候可以加剪枝,比如左儿子没有被访问那么不需要询问右儿子的情况下直接访问右儿子。
-
交互题不一定要分析询问返回值,可能是多个询问返回值放在一起运算得到信息。
-
构造题有 \(n\) 类东西的构造题考虑先缩小值域至 \(0/1\),然后按照值域 \(mid\) 进行分治构造。