常见技巧总结

常见技巧总结

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. 对于一些题目,其操作正向考虑较难计算贡献,我们可以反向考虑最后一次操作的贡献。

2 图论

  1. 对于边权为 \(1\) 的图,常采用 Bfs 求解各种问题。
  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. 一个森林的连通块个数是 点数 - 边数。
  4. 在树上维护路径的题目中,需要考虑路径可以拆成哪几段路径以更方便的处理。最常考虑的就是 \(\text{LCA}\) 以及根节点。
  5. 对于求一些路径总和的题目,可以考虑拆成每一条边的贡献。
  6. 图上有一个经典容斥套路:假如要求 \(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}\)
  7. 树上启发式合并的复杂度很优秀,关键的时候需要想能否通过启发式合并优化复杂度

3 二进制 / 位运算

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

4 数据结构

  1. 序列上进行操作的题目,如果不像传统的数据结构题,可以先去发现一些性质,然后可能会发现答案与某一数组的求解关系较大,而这个数组的求解就可能用到数据结构。
  2. 当某一操作或某一贡献只在特定区间内存在时,考虑线段树分治。
  3. 可以在平衡树上的每一个节点维护一个区间,当需要查询其子区间信息的时候将该节点分裂即可。
  4. 假如要维护的东西不是一个数值而必须维护一些数组,考虑减小其规模,这样仍可以在线段树上暴力合并求解。
  5. 二分加线段树的复杂度是两个老哥,由于线段树的大常数这样的做法其实往往难以通过。考虑直接在线段树上二分来替代。
  6. 序列上的问题除了考虑传统数据结构外,也可以考虑在前缀和 / 差分数组上操作,或许会发现更为优秀的性质。
  7. 线段树懒标记的本质是记录不同时间对该区间的操作。因此先下放的懒标记的操作时间一定是小于当前的懒标记的。可以通过这个性质维护一些有时间限制的操作。
  8. 注意当操作看上去较为难维护的时候能不能通过离线 / 扫描线等方式简化。

5 其它算法

5.1 贪心

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

5.2 倍增

  1. 在序列上按照同一种方式走的时候,可以考虑倍增预处理辅助求解。

5.3 中位数

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

6 数学

6.1 组合数学

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

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. 当题目的要求有非常显然的必要条件的时候,可以考虑这个条件是否充分。如果不充分但是有一定的充分性,可以考虑随机赋权值以增加其充分的概率来求解问题。
posted @ 2024-02-27 19:10  UKE_Automation  阅读(59)  评论(0编辑  收藏  举报