trick记录

前言

记录一下有用的trick

统计

  1. 有上下界, 并且答案和每个数位有关的不一定是数位 dp , 还可以考虑在某个地方后面的数变自由, 也就是可以随便选, 经常用在二进制中
  2. 如果是区间维护的问题, 并且这个区间会进行比较复杂的操作, 那么就可以考虑用矩阵来表示操作。
  3. 分数的操作其实可以考虑矩阵, 见[NOI2021] 密码箱
  4. 均摊的基础在于势能的变化, 所以做题时观察一些有上限并且每次操作会被改变, 且只减或增加的势能是常数的量, 如Adam and Tree就有量点向上跑的最大颜色数 \(\leq \log{n}\)
  5. \(b\) 分配给 \(a\) 求最值的问题可以考虑网络流, 网络流可以和最小割相互转化, 并且其实, 最小割一般来说更容易有简单计算的方法, 如 Four Suits
  6. 二分图匹配问提可以用hall定理减少边数
  7. 如果要统计任意两点间有通过有约束(这里特指数量约束)条边的最值(长度的最大最小)或数量, 可以考虑矩阵, 如旅游路线
  8. 位运算的题一定要考虑分成一位一位能不能优化
  9. 平方数可以表示为质因数分解后每个质数的系数为偶数, 所以乘积为平方数的关系是有传递性的, 即 \(a \times b = x^2, b \times c = y^2 \Rightarrow a \times c = z^2 (a, b, c, x, y, z \in Z)\)
  10. 如果某一个量他的和是一个约小于 \(1\times10^5\) 的量, 那么可以考虑根号分治
  11. 对于某些实在只会比较暴力做法的题, 可以多想几个时间复杂度无法通过但时间复杂度表达式不同的做法, 也许可以通过根号分治平衡复杂度。
  12. 一道题如果是有编号的点建树(或者树形结构), 那么就要想到prufer序列
  13. 有些时候贪心策略无法满足需求的原因是贪心策略只是当前类别抉择的最优, 而不是题目限制下的最优(如背包问题, 贪心的抉择得到的结果是当前贪心出来的物体总体积的最优, 而不是 \(m\) 体积限制下的最优), 那么有时候限制很大的时候, 贪心在大部分情况下最优, 只有最后的小部分无法达到最优, 就可以先考虑贪心缩小范围, 再进行 dp , 如Topcoder 13859 ClassicProblem
  14. 如果一个问题是问的进行一种操作, 问在进行某轮(一般较大)操作后的结果, 这种题不是通式就是在某一步后有循环, 如[CCO2021] Through Another Maze Darkly
  15. 一个问题如果和 \(\gcd()\) 有关, 且可以转化成 \([gcd() = k]\) 的式子, 那么就要想到莫比乌斯反演, 核心式子是 \(\sum\limits_{i | x} \mu(i) = [x == 1]\) , 一般来说用到的是 \([gcd(x, y) = 1] = \sum\limits_{i | x, y} \mu(i)\)
  16. 有时候 dp 的状态数很多, 那么不要急着考虑优化状态, 可以想一想能否将一些状态合成一个, 这些状态可能有通式或相同的值, 如CF1894 H Asterism Stream
  17. 树上点对的最长距离有几种方法, 如树分治(可能是点分也可能是边分), 建虚树然后对每个点考虑其为 \(lca\) 时的答案, 合并两个点集(总点集的最长链的两端点只可能是两个点集中最长链的两端点四选二得来)等。 有一些恶心的题, 需要这些方法嵌套, 如 [WC2018] 通道Twin Binary Trees
  18. \(n^{\frac{5}{3}}\) 能过 \(5e4\) , 这刚好是分块了以后暴力重构是 \(B^2\) 的算法, 如 最大连续子段和
  19. 李超树合并的总时间复杂度是 \(O(n\log^2n)\) 的, 如果每个函数没有值域限制, 那么甚至可以做到 \(O(n\log n)\), 所以不要写启发式合并, 会多一个 \(\log\)
  20. 一个 dp 或者其它什么东西, 如果他的差分是 \(0/1\) 那么可以考虑差分以后转移或者干其它的, 因为 \(0/1\) 意味着可能可以用 bitset 优化, 如 Square Subsequences
  21. \(O(\frac{n^3}{w})\) 如果常数小, 能过 \(n = 3000\) , 其中 \(w = 64(32)\)
  22. 可持久化分块是一种插入 \(O(\sqrt{n})\) , 查询 \(O(1)\) 的东西, 可以解决诸如 \(A[1...i]\) 中小于 \(y\) 的数的个数这种问题, 也就是说, 可以让用类似莫队的方法查询区间逆序对的时候的 BIT 维护变成可持久化分块, 就可以让时间复杂度减少一个 \(\log\)
  23. 有时候, 线段树上可以保留一个线性长度的信息, 如凸包等, 如[Ynoi2015] 世上最幸福的女孩
  24. 分块套线段树不失为一种很好的想法, 这样我们甚至可以用如23讲的方法去构造线段树, 想、因为只要建树的时间复杂度是 \(O(s\log s)\) , 那么散块修改就是合法的, 如[Ynoi2018] 末日时在做什么?有没有空?可以来拯救吗?高时间复杂度做法( \(O(n\sqrt{n}\log{n})\)
  25. 树形背包可以做到 \(O(nm)\)
  26. 较少的几个数的 \(\min/\max\) 进行贡献, 可以考虑转换成满足限制的情况数乘以某个值, 进而使用 cdq 计算, 如 [NOI Online 2022 提高组] 如何正确地排序[NOI Online 2021 提高组] 岛屿探险
  27. 如果要求的式子有 \(\min\)\(\max\) 并且求的东西完全一致, 可以考虑用 minmax 反演把最大的一项消除, 如 [NOI Online 2022 提高组] 如何正确地排序
  28. 打表作为一种非常前卫的方法, 有时候挺有用的
  29. 为了保证某一决策的优越性, 有时候我们可以考虑这个决策不优时就将贡献直接换成其它决策组合, 这样即保证了当前决策的最优性又不会影响答案, 如爵士之旅 Jazz Journey
  30. 匹配之类的问题求最小(大)代价别总想着费用流, KM, 还可能是最小割, 如[ABC259G] Grid Card Game
  31. 每一行, 每一列都有要求, 每一个位置会对这一行这一列造成影响, 要考虑行列匹配。
  32. 平面上点的问题, 一定要记得 KDtree
  33. 从搜索的角度看字符串和数字大小, 字典序就是深搜, 数比较大小就是广搜
  34. 排列+将 \(i\) 替换为 \(a_i\) , 要想到拆成很多个环
  35. 表达式中的变化量是很烦人的, 所以我们要想到去消除他, 而两边同时乘上一个东西, 是一个不错的想法, 如Many CRT
  36. 互质会让模或者一些运算有更多的性质, 我们应该想到用一点操作将问题变得互质, 如Many CRT
  37. 如果某一种修改操作分成不同类进行的话, 那有可能其对答案的影响是相同的, 如[NOI2021] 密码箱
  38. 有时候我们贡献的形式会是如 \((x, y, z ...)\) 这样很多个特定的变量影响的, 如果这个问题带修, 我们可以考虑将贡献记录在一些值上面, 这样修改的时候我们只用考虑本次修改对这些的影响。 重要的是, 我们一定要多考虑几种记录, 因为大概率其中有一种影响比较好记录, 如夏虫
  39. 如果摸一个修改的影响会分成修改位置对其它位置的影响和修改位置修改后对自身答案的影响, 一定要记得可能自身的影响可以直接计算, 并不用要考虑变化量, 如夏虫
  40. 如果遇到需要让某些值同时在一个集合或同时不在, 或者在当前集合的数有一些要求, 那么hash不失为一种非常优秀的解决方式, 如[POI2015] POD[CSP-S 2022] 星战
  41. 匹配问题要想到 AC 自动机, 有时候甚至可能是对询问建 AC 自动机, 如Upside Down
  42. 给出两个串 |s| , |t| , 要求 |s| 中所有为 |t| 后缀的前缀, 可以找 |s| 中最长的为 |t| 的后缀的前缀, 然后不停地跳border , 这样转化后可以有将border拆成 \(\log\) 个等差数列或者在失配树上的操作, 如Upside Down
  43. 后缀数组也能求|s| 中最长的为 |t| 的后缀的前缀, 可以将问题转化为求|t| 中最长的为 |s| 的前缀的后缀, 然后二分 |s| 在 |t| 建立的后缀数组的位置, 如 Upside Down
  44. meet in middle 可以将指数级别的状态直接开根, 所以遇到搜索的状态数开根能过得情况, 并且前后状态合并容易, 可以考虑, 如Playoff Restoration
  45. 环的剖分问题一定要考虑一刀切在 \(1\) 前和一刀切在 \(n\) 后是一样的, 如 [POI2015] POD
  46. 如果问题是要求出所有的满足某个限制的数对, 并且这个数对有数量限制, 那么可以考虑将物品以某个条件分块, 使每一块中的数因为答案总数的限制而不会太多(及因为这个快可以分成很少的几个块, 使得再次拆成的块中两两都满足条件)
  47. 如果遇到某个问题是一个 A 类型变量(如下标)对应一到二个 B 类型变量, 可以选择在这两个 B 类型变量间连边, 如 [省选联考 2023] 填数游戏Royal Questions
  48. 组合数很容易转化成多项式, 所以如果遇到状态是 \(dp_{(i, j)}\)\(dp_i\) 转移到 \(dp_{i + 1}\) 的时候是一个 \(n^2\) 转移, 并且转移的系数只跟两个位置的偏移量有关(即 \(dp_{(i, j)}\) 转移到 \(dp_{(i + 1, k)}\) 之和 \(k - j\) 有关) 的时候(特别注意组合数), 可以考虑多项式乘法把转移优化成 \(O(n\log{n})\)\(O(3^{\log_2{n}})\) , 如 Tiles
  49. \(n=70\) 可能意味这可以以 \(2^{\frac{n}{4}}\) 甚至 \(n2^{\frac{n}{4}}\) 时间运行, Abandoning Roads
  50. 如果序列产生循环, 但是我们并不知道循环的序列具体长什么样, 可以考虑取一个位置经过长度轮循环时依次是多少, 如 [IOI2009] Archery
  51. dp的状态和值应该记录我们关心什么,同一个问题不一定只有一种关心情况,可能是我在 A 时关心一件事, 我在 B 时关心另外的事, 我们将其分成两个 DP 就可以减少记录不关心的东西,从而降低时间复杂度, 如 Favorite Game
  52. dp 的时候,我们可能会遇到某种操作对应着类似于 \((a, b)\) 的转移,即对于 dp 状态 \(dp_{i, j}\) 我们需要为第一维选择一个 \(a\) 为第二维选择一个 \(b\) ,然后进行转移, 但我们可能并不用这样, 因为也许我们能把转移拆成两部分, 即 \((a, b)\) 可以拆成 \(a\)\(b\) 两种操作,这样就可以降低转移的复杂度, 如 Mole and Abandoned Mine
  53. 牢记NP问题,遇到直接暴力剪枝, 如[PA 2022] Mędrcy
  54. 有时候,我们可以发现一些大于什么什么就肯定不优的事情,这时候就可以考虑只记录少许状态即可,至于怎么发现,我们可以考虑去掉一些看着就比较碍眼的条件,一般来说这时候可能会有一些很显然的做法,我们接着考虑加上去掉的条件以后对答案的影响,这很可能影响并不大,稍加思考可能就能得出,如Bear and Cavalry
  55. 要求一个非负数列单调不降,我们可以考虑其等价于两个东西,第一个是一个初始全为 \(0\) 的序列对于每个后缀可以尽行任意多次后缀加, (有最大值的上限就考虑操作次数的和小于序列最大值要求的上界) ,第二个类似,但需要有上界,首先将序列初始值全赋为上界,然后类似的操作对于前缀, 这两种看上去相同,但因为前后缀的原因在题目里的表现不尽相同,所以考虑但这种方法的时候不要只考虑一种,两种都要考虑,如 [AGC041D] Problem Scores
  56. 切记一切和小于等于 \(n\) 的数列都有不同的数的个数小于等于 \(\sqrt{n}\) 的性质(证明简单根号分治即可), 这经常被用来保证时间复杂度, 并且如果我们设 \(c_i\)\(i\) 出现的次数的话,那么有 \(\sum\limits_{c_i \ge 1}\log{c_i} \le \sqrt{n}\) (这个时间就是快速幂的时间复杂度), 如石子合并
  57. 枚举子集可以用 for(int i = v; i; i = v & (i - 1)) 原理就类似于把没用的位抛弃后的 2 进制减法。
  58. 可以以选定某个点为连通块(树)的中心(可以在边上),与其距离大于等于某个值的点都删除来表示直径(书上原),如街头庆典
  59. 树链剖分不仅可以方便树上信息的维护,有时候将重链和轻链分开转移 dp 也可能会有奇效(甚至在 ddp 当中这样做), 如街头庆典「2019 集训队互测 Day 3」公园
  60. 如果一道差分约束的题所有边只可能和一个变量有关, 那么可以证明符合条件的变量是一段区间, 具体证明是建出来的图可以拆分成许多个简单环,每个环都可以表示为一个关于变量的函数表达式,而这个函数表达式解出来的合法范围是连续的一段区间,多个连续的区间的并是一段连续的区间,同时我们也可以用这个性质二分出合法的区间, 就每次二分后就看负环上变量的系数是正是负,如YamanoteLine
  61. 一道题满足以下条件的时候他很可能就是(记忆化)搜索:1.数据范围很小,2.朴素搜索的状态中有大量无用的,3.可能有些重复状态,4.不好清楚地定义出转移,如Mountains
  62. 永远记住二分图中最大点独立集,最大匹配,最小点覆盖可以相互转化,网络流中最小割,最大流可以相互转化,重点不是转化,而是相互,朴素算法肯定只能最大匹配,最大流,但对于特殊图来说,最优算法不一定是这俩
  63. 如果一个值只会产生 0 或 a_i 的贡献, 而这个贡献可以被表示为选择,即我只不能重复地产生贡献,并且我们接下来的选择是以区间为单位的,(说着有点抽象,一个例子就是有很多个区间,我们的询问是询问这段区间中的区间的并的大小, 那么对于每个位置来说,他只有被区间选择和没被区间选择两种状态),如果我们询问的区间并不是单独的一个区间,而是需要用到扫描线的那种包含在某一区间内的区间,那么我们为了服务于扫描线,我们考虑扫描线移动的时候可以更新对于每个值来说,离当前右端点最近的能使它产生贡献的选择(刚才说的例子就可以使用珂朵莉树来维护这个选择), 如ntervals of Intervals
  64. 对于那种实数范围内随机的问题,我们可以考虑将数分成整数和小数考虑,因为小数的范围问题,并且大多数题目给出的所有数据都是整数,所以我们可能对小数的要求没那么严苛,如 [pkusc2021]招新
  65. 如果一个问题询问可以表示为计算能产生贡献的值的贡献和,修改能表示为更改某个值的贡献范围(注意,这里的更改范围比并不一定是真能修改到,我们计算的是贡献范围,具体是否能贡献的条件直接看),那么我们可以尝试将问题转化成限制问题,即满足什么样的条件的可以产生贡献,再考虑将这个条件转化成大小条件,这就成了一个偏序问题,可以使用cdq来解决, 如[pkusc2021]逛街的三维偏序做法
  66. 前缀最大(最小)(即满足 \(\max_{i = l}^{j - 1}(a_i) < a_j\) 的位置,注意这里的 \(i\) 并不一定是从头开始)可以考虑对于每个点都像它之后比它大(小)的点连边(没有就连向末尾之后),这样可以形成一棵树,而对一个区间的查询其实合法的位置就是树上的一条链,如[pkusc2021]逛街的树上做法。
  67. 有一些题目,有明显的贪心策略,这里的贪心策略是某一些选择越往前明显越优,那么我们对这些选择考虑,一个位置能被这些选择选择当且仅当其之前的位置都被选择满了,那么这个时候,我们就会发现,其实整个序列或者啥的被分成了两个部分,前面是全选满的,后面是全没选的,中间有一个是选了但不一定选满的,我们可以两部分分开考虑,首先中间这个位置一般来说是好求的,然后再去维护两边的,这样就可以实现待修,如[pkusc2021]代金券
  68. 没有链修改(子树修改)的树上链问题,要想到树上差分,如[pkusc2021]逛街
  69. 我们知道,0/1 背包可以解决 \(\frac{\sum a_i}{\sum b_i}\) 的最值问题, 那么如果是求 \(\sum a_i \sum b_i\) 的最值呢?0/1 分数规划显然不太行,所以我们考虑将所有可能的 \((\sum a_i, \sum b_i)\) 画在直角坐标系上,假设答案是 \(ans\) , 那么能取到最优解的点 \((x, y)\) 满足 \(xy = ans\) , 这就是一个反比例函数,所以我们考虑如果我们要求的最值是最小值,那么我们可以枚举过答案点的线的斜率,因为我们发现所有点都位于过答案反比例函数任意切线的上方,所以只要枚举过存在的答案点的切线的斜率即可(注意不一定是切线,我们只需要对所有可能的切线算出来的答案求最值即可),然后又有答案点满足 \(\forall (x_i, y_i), y_i \ge kx_i + (y_{ans} - kx_{ans})\) , 即 \(\forall (x_i, y_i), y_i - kx_i \ge y_{ans} - kx_{ans}\) , 所以转化成了类似于 0/1 分数规划的形式。
  70. 如果一个 dp 转移和元素的排名有关,但又不和全局排名有关,之和相对排名有关,那不妨不要将 dp 状态写为全局排名,写成其在前缀中的排名就行, 转移时下一个数直接插入可以保证不会算重序列, 如[pkusc2021]招新
  71. 一个问题如果做不出来,要么往暴力的方向考虑,要么往简单的方向考虑,比如说如果一道题他是有不同权值的,并且权值都是正数(也可能不需要这个条件),那么我们可以想一想权值全为 \(1\) 的时候怎么做,这时候一般来说都有一些很明显的性质使得你这么转化后可以转化回来(这些性质是能保证转化前后的问题相等或相似),比如说递增或者大于包含的,如Gym - 102900F
  72. 如果某个问题是在最小生成树上做树 dp , 那不妨尝试将 dp 改在克鲁斯卡尔生成树上进行, 毕竟二叉树的性质更多一些。
  73. 全部完成的步数可以转化成最晚完成的完成,然后就可以用 minmax 容斥优化, 如TopCoder - 12607
  74. 两个数挨在一起可以抵消可以看成是某个群中的 \(x \times x^{-1} = e\) , 这样的话就可以考虑去把元素附成一个群里的元素(根据情况,要求运算是否满足交换律,如果改变位置无影响,则可以用交换律)(关于如何赋,我们可以考虑比如相同元素可以抵消,则按奇偶分别附成 \(x\)\(x^{-1}\)), 这样问题就能用 Hash 解决了,如[CSP-S 2023] 消消乐
  75. dp 的值如果远小于状态数,并且可能有特殊性质的时候(也许不用这条),我们要想到交换状态数和 dp 值, 从而降低时间复杂度, 如Bulbasaur
  76. 小于等于 \(10^{12}\) 的数的质因子只有 \(11\) 个不同的,根据问题需要(比如求 gcd 或者 lcm 之类的) 可以进行状压 dp , 如[Professional layer]
    (https://www.luogu.com.cn/problem/CF1103D)
  77. 如果问题的形式可以转化成 \(\forall S,\sum\limits_{i \in S}a_i \le \sum\limits_{i \in \{L_j | j \in S\}} b_i\) , 那么我们要想到这个形式就是 Hall 定理的形式, 故问题可以转化成二分图匹配, 如 Chests and Keys
  78. 如果进行 dp 时有些状态并不需要转移计算,而是可以直接统计,那我们尽量采取直接统计的方法,这样可以减少状态, 比如 trie 树上跑异或值小于等于某个数 \(k\) 的时候,可以发现对于两个数异或值不等于 \(k\) 的时候(因为此时必定是某一位 \(k\)\(1\) , 我们却选择了走成 \(0\) ),其实可以直接计算,这样我们就不用计算这种状态了,所以 dp 状态就能少一维(本来要记录两个数的位置,现在只用记录一个,另一个直接异或)(可以拓展到三个甚至多个), 如 Keep XOR Low
  79. 多个数的异或问题不是线性基就是 trie 树 dp , 其它方法可能有, 但罕见。
  80. 有时候某个操作(如排序)会使得整个序列变得规律, 有一些性质(如排序), 而我们又有在末尾插入的操作, 插入的数又因为没有这个奇特的操作所以很容易维护, 那么我们可以将两部分分开维护, 如 [Ynoi2011] 竞赛实验班
  81. 欧拉环游的路径总长度等于树的边权和的两倍, 欧拉环游序可以表示为一个包含所有叶子节点的集合 \(S\) 在按照 dfn 序排序后, 相邻两个节点直接的路径(最后一个和第一个相邻)组合出来的序列, 虚树的叶子全是关键点, 所以我们就有了快速求虚树边权和的方法, 如Archaeology
  82. 树的重心不仅可以用来平衡时间复杂度, 我们更可以考虑虚树的重心, 可以发现, 这时候以重心为根很有性质, 除了根以外的点, 都有子树内的关键点个数小于子树外的关键点个数, 如Tenzing and Tree
  83. 树上选点问题都可以从虚树的角度考虑, 不一定要建出虚树, 但可以启发算法
  84. \(\lfloor \frac{r}{x}\rfloor - \lfloor \frac{l}{x}\rfloor\) 不具有单调性, 但我们考虑 \(\lfloor \frac{l}{x}\rfloor\) 为定值的时候其具有单调性, 如 Anniversary
  85. 如果一个问题的状态有一些明显的依赖(如左右括号需要匹配)我们可以考虑将其对应到一个信息比较少的状态(比如删除一些可以被唯一确定位置的括号),使得两种状态一一对应, 这样我们就能减少 DP 状态, 如 Unambiguous Arithmetic Expression
  86. 从感觉上状态很小的东西,我们可以在本地打表出一个可能的范围, 如TallyMarksSystem
  87. 方差的计算有一种方法是取出集合的所有无序点对, 分别计算其差值的平方, 然后再取平均即可, 如AverageVarianceSubtree
  88. 两个数的操作产生贡献可以考虑连边, 如果问题可以转化为将一张图的点分成两个集合, 使两个集合的导出子图中的边的最小值最大, 可以考虑转化成割掉最小的那些边, 从而转化成将最小生成树上的边割掉, 如XOR Partition
  89. 如果我们需要进行非常多次的二分(非线性次),可以考虑使用整体二分降低时间复杂度(询问线性,但二分次数不是线性,整体二分后二分就变成了双指针)。
  90. 查询区间存在的概率可以转化成区间左(右)端点存在的概率, 这样就只用对点的概率查询。
  91. \(a\) 序列可以尽心若干次选择两个相等的位置翻转该字串可以得到 \(b\) 序列的充要条件是 \(\{a_i, a_{i + 1}\}\)(相邻元素构成的无序二元组)构成的可重集与 \(\{b_i, b_{i + 1}\}\) 构成的可重集相等。
  92. wqs二分不太支持多次询问, 凸包不太支持不确定区间的询问, 那么我们可以考虑先在线段树上通过闵科夫斯基和去初始化每个线段树节点的凸包, 接着在查询的时候将所需的区间找出来, 然用wqs二分将凸包变成值, 这样就可以很方便地合并了。
posted @ 2023-10-03 10:40  小篪篪  阅读(68)  评论(0编辑  收藏  举报
Live2D