常见技巧总结

常见技巧总结

1 动态规划

  1. 对于贡献存在于整棵树上的 dp,考虑分两次分别计算子树内和子树外的贡献。也就是传统的 up and down。
  2. AC 自动机上 dp 一般考虑设状态为:\(dp(i,j)\) 表示枚举到 AC 自动机上第 \(i\) 个点,字符串长度为 \(j\) 时的方案数。在 \(n\) 较小的时候也可以再加上一维状压。
  3. 当 dp 题目中出现差值的时候,可以考虑差值 dp。具体的,状态中设一维 \(x\),dp 值记录与 \(x\) 的差值,答案就是 \(x+dp(x)\)
  4. 灵活运用刷表法和填表法进行转移。
  5. 状压 dp 时,for (int T = S; T; T = (T - 1) & S) 可以不重不漏枚举 S 二进制下的所有子集。
  6. 在区间 dp 时,单次考虑取一个或者取一段区间所得出的方程不同,最后做法的难易程度也可能不同,需要灵活处理。
  7. 树形 dp 时,我们可以考虑对于每一条重链只开一个 dp 数组进行转移,在重链头进行合并,可以将空间复杂度优化至 \(\log\)
  8. 在 dp 状态中,如果有一维的状态很大而难以记录,考虑矩阵快速幂优化,或者去发现一些特殊性质以优化状态设计。
  9. 在 dp 转移中,如果出现两维需要一起转移,然而两维实际上是独立的时候,可以考虑分步转移以优化复杂度。
  10. 对于配对问题,一种经典做法就是记录前面有多少个位置还没有配上,进而计算贡献。
  11. 对于一些题目,其操作正向考虑较难计算贡献,我们可以反向考虑最后一次操作的贡献。
  12. 考虑对转移顺序模糊的步骤强制钦定顺序以保证正确性。
  13. 状态中定义域过大而值域较小的时候考虑交换值域定义域。

2 图论

2.1 普通图

  1. 对于边权为 \(1\) 的图,常采用 Bfs 求解各种问题。
  2. 对于求一些路径总和的题目,可以考虑拆成每一条边的贡献。
  3. 图上有一个经典容斥套路:假如要求 \(n\) 个点的图满足某一条件且联通,设 \(f(i)\) 表示 \(i\) 个节点的满足条件且联通的图的方案数,\(g(i)\) 表示 \(i\) 个节点的满足条件的图的方案数。如果可以简单的求出 \(g(i)\),就可以用 \(g(i)\) 求出 \(f(i)\) 了。具体的,对于一个 \(f(i)\),用其方案数应该是 \(g(i)\) 减去不联通的图的方案数。我们钦定不联通的图中,\(1\) 号点所在连通块大小为 \(j\),则此时不合法方案数就是 \(f(j)g(i-j)\binom{i-1}{j-1}\)
  4. 建图论模型时如果出现多个点 \(U\) 连向多个点 \(V\),复杂度达到 \(O(n^2)\) 的时候,考虑建立一个中转点 \(x\),让 \(U\to x\to V\),可以将复杂度优化至 \(O(n)\)

2.2 树

  1. 一个森林的连通块个数是 点数 - 边数。
  2. 树上 \(n\) 个关键点之间形成的最小联通子图的大小为 \(\sum\limits_{i=1}^n \operatorname{dep}_{a_i}-\sum\limits_{i=1}^{n-1}\operatorname{dep}_{\operatorname{lca}(a_i,a_i+1)}-{\operatorname{dep}_{\operatorname{lca}(a_1,a_2,a_3,\cdots,a_n)}}\)(按 DFS 序排序后)。
  3. 在树上维护路径的题目中,需要考虑路径可以拆成哪几段路径以更方便的处理。最常考虑的就是 \(\text{LCA}\) 以及根节点。
  4. 树上启发式合并的复杂度很优秀,关键的时候需要想能否通过启发式合并优化复杂度。
  5. 树上三个点有一个很好的性质:任意两个点的 \(\text{lca}\) 中至少两个是一样的。由此可以衍生出更多性质,例如已知三个点之间的距离可以求一个点在谁的子树中等。
  6. 遇到一类树上构造问题时,可以先从叶子开始考虑,因为叶子度数为 \(1\) 的特性可能会简化情况,并且删去叶子后问题可以递归求解。
  7. 遇到树上和子树大小 / 连通块大小有关限制时,可以考虑以重心为根发掘性质。

2.3 特殊图

  1. 竞赛图缩点后会形成一条链,每个点会向后面的每个点连边。
  2. 竞赛图上 SCC 计数有一个套路:由于竞赛图缩点后会形成一条链,所以每个强联通分量会对应一个前缀,而这个前缀满足这个前缀中所有点都指向外面的点。我们称这样的一个前缀为一个割集,而统计 SCC 数量实际上就是统计割集数量。

3 二进制 / 位运算

  1. 几个二进制数之间位运算最优性问题可以考虑 0-1 Trie。
  2. 对于某些二进制问题可以考虑将每一位拆开计算。

4 数据结构

  1. 序列上进行操作的题目,如果不像传统的数据结构题,可以先去发现一些性质,然后可能会发现答案与某一数组的求解关系较大,而这个数组的求解就可能用到数据结构。
  2. 当某一操作或某一贡献只在特定区间内存在时,考虑线段树分治。
  3. 可以在平衡树上的每一个节点维护一个区间,当需要查询其子区间信息的时候将该节点分裂即可。
  4. 假如要维护的东西不是一个数值而必须维护一些数组,考虑减小其规模,这样仍可以在线段树上暴力合并求解。
  5. 二分加线段树的复杂度是两个老哥,由于线段树的大常数这样的做法其实往往难以通过。考虑直接在线段树上二分来替代。
  6. 序列上的问题除了考虑传统数据结构外,也可以考虑在前缀和 / 差分数组上操作,或许会发现更为优秀的性质。
  7. 线段树懒标记的本质是记录不同时间对该区间的操作。因此先下放的懒标记的操作时间一定是小于当前的懒标记的。可以通过这个性质维护一些有时间限制的操作。
  8. 注意当操作看上去较为难维护的时候能不能通过离线 / 扫描线等方式简化。
  9. 珂朵莉树在有特殊性质下可以保证 \(O(n\log n)\) 的复杂度,题目中出现相关信息的时候要注意。
  10. 在一个无重复元素的序列上找某一元素左 / 右第 \(k\) 个比自己大的元素可以使用链表求解。具体的,对原序列维护链表,然后按照元素大小从小到大删去元素。这样求解一个点的答案的时候,比这个元素小的元素已经全部清除,然后跳链表即可求解。
  11. 使用莫队时,由于莫队修改复杂度 \(O(\sqrt n)\) 而查询复杂度是 \(O(1)\) 的,所以如果搭配一个修改较快而查询较慢的数据结构复杂度依然不变。最经典的例子就是莫队套树状数组的复杂度是 \(O(n\sqrt n\log n)\) 的,但是如果把树状数组改成值域分块的话复杂度就还是 \(O(n\sqrt n)\) 的。
  12. 题面中出现了明显的一组乘法关系形如 \(v\times a_v\le n\) 时,考虑根号分治。

5 其它算法

5.1 贪心

  1. 求解贪心最简单的做法就是考虑邻项交换法,以此算出排序函数。
  2. 需要注意的是排序函数需要满足四个性质:非自反性、非对称性、传递性、非可比性的传递性。如果不满足需要考虑怎样修正。
  3. 传统贪心无法解决的问题可以考虑反悔贪心。具体的就是用一个堆去维护已经做过的决策,如果决策放到现在做更优就更换。

5.2 倍增

  1. 在序列上按照同一种方式走的时候,可以考虑倍增预处理辅助求解。
  2. 同一种数据要多次复制时,可以考虑倍增加快复制。
  3. 矩阵快速幂的时候可以通过观察矩阵性质将单次乘法的复杂度做到 \(O(n^2)\),这样就可以优化时间复杂度。

5.3 中位数

  1. 传统求解区间中位数的方式是对顶堆,即维护两个大小相差不超过 \(1\) 的堆,插入时动态调整即可。利用 set 就可以支持删除操作。
  2. 数据随机的情况下,每一次增加一个数对中位数的影响是常数级的,于是可以直接暴力。

5.4 二分 / 分治

  1. 只要答案具有单调性,就可以考虑二分答案,这样往往可以将问题转化为简单的判定性问题求解。
  2. 求出序列中排除掉某个数的贡献,可以考虑分治求解。具体的,先将 \([l,mid]\) 的元素加入,递归到右半边;然后撤销这部分贡献,加入 \([mid+1,r]\) 的元素,递归到左半边。如此一来,当我们递归到区间 \([l,r]\) 时,所有不在该区间内的数字都被加入,我们在叶子节点处统计一下贡献即可。

6 数学

6.1 组合数学

  1. 在题目中要求某一个值的 \(k\) 次方之和时,我们常考虑 dp,设状态为 \(dp(i)\) 表示值的 \(i\) 次方之和,然后根据二项式定理进行合并即可。
  2. 和式有四大变换技巧需要注意:交换枚举顺序、改变指标变量、替换条件式、分离变量。如有大量求和式时考虑用如上方式优化复杂度。
  3. 对于一些组合数求和,可以从组合意义上考虑等价的表达,以优化时间复杂度。
  4. 对于一些所求答案形式较为奇怪的计数题,考虑能否赋予其组合意义以辅助求解。
  5. 在计算答案的过程中,如果发现难以保证最后的目标值恰好等于题目要求,但是可以保证这个值至少等于某个数,可以考虑设 \(g(i)\) 表示目标值至少为 \(i\) 的方案数,利用二项式反演来得出最后的答案 \(f(i)\)
  6. 计算最大值为某个数的方案数时,可以考虑利用容斥,算出最大值小于等于 \(x\) 的方案数 \(f(x)\),则最大值为 \(x\) 的方案数就是 \(f(x)-f(x-1)\)。最小值同理。
  7. 在模数较小的时候求解组合数的和,可以考虑利用 Lucas 定理,将组合数拆成 \(p\) 进制下每一位的结果相乘,然后利用数位 dp 求解。

6.2 概率和期望

  1. 对于计数题,有时可以转化为答案的期望乘上方案数,这样可能会有更好的性质。
  2. 对于图上求期望 / 概率,是 DAG 的可以拓扑排序 dp,非 DAG 的可以考虑高斯消元。

6.3 数论

  1. 题目中要求解一些数字的 \(\gcd,\text{lcm}\) 为固定值时,考虑经典的 \(\gcd\) 容斥。设 \(f(x)\) 表示算出的 \(\gcd\) 为目标值 \(x\) 的倍数的方案数,利用莫比乌斯反演求出原函数值。
  2. 对于大量相同形式的求和,如果时间复杂度不支持暴力计算,考虑仿照快速幂的方式倍增求解。
  3. 整数的质因数分解式不仅仅用于传统数论题,它也用在估计答案上界、优化状态记录等。

7 杂项

  1. 多次多个元素的贡献可以拆成多次单个元素贡献的和,即考虑每一个个体对整体的贡献。
  2. 对于判断可以或不可以的题目,可以先看可以或不可以时满足的条件,以此发现一些性质后去做。
  3. 当出现 至少一个 等字眼时,考虑用总的情况减去全部非法的情况。
  4. 正序做困难时考虑反向做(包括但不限于组合数学、序列问题、图上删边等问题)。
  5. 遇到取整的时候不妨考虑 \(\pm0.5\)
  6. 特殊情况下可以考虑数据随机时更暴力的解法。
  7. 对于某些序列问题,可以考虑能否通过连边将其转化为树上问题求解。
  8. 当题目中出现区间 \(\max,\min\) 的要求的时候,常见处理方式有两种:单调栈去算一个数对区间的贡献,或者笛卡尔树固定一个区间的最值算贡献。两者的本质都是固定一个最值算贡献。
  9. 当题目的要求有非常显然的必要条件的时候,可以考虑这个条件是否充分。如果不充分但是有一定的充分性,可以考虑随机赋权值以增加其充分的概率来求解问题。
  10. 在暴力算法中,看看有没有什么特殊的性质(例如指针的值每一次至少翻一倍),可以使其复杂度降低。
  11. 将题目转化为一些模型或式子之后,也不要忘了题目的实际背景,有时可以从中得到一些性质。
  12. 在某些题目中,不妨先假设出某些关键值,然后再分析性质。
  13. 分析题目时可以先排除掉一些平凡的情况后再做。
  14. 题目中要求有关最值的时候,可以考虑把所有值排序后一一加入算贡献。
  15. 对于排序等一类只考虑元素相对大小关系的题目,考虑枚举一个阈值 \(k\),将 \(>k\) 的元素赋值为 \(1\)\(\le k\) 的元素赋值为 \(0\) 之后进行求解。
posted @ 2024-02-27 19:10  UKE_Automation  阅读(186)  评论(0)    收藏  举报