典
典 10 - 错误
- 用 \(\%d\) 输入 \(long\ long\)
- 书接上回:用 \(\%lld\) 输入 \(int\)
- 长度为 \(m\) 的 \(dp\) 数组开 \(n\)
- 函数不加 \(return\) (加倍:在分块等中间需要特判并操作和返回的函数中仅操作,未返回)
- \(if\) 后面加分号
- 迪杰斯特拉优化版用大根堆【超级加倍】
- \(for\) 循环里面的 \(for\) 循环用 \(i\)
- 图论 \(m\) 比 \(n\) 大,但是存数据时全按 \(n\) 的范围设数组
- 在做涉及 \(char、string\) 等类型的题目时,将 \(int、long long\) 类型的变量设置为字符串类型
- 非 \(void\) 类型的
dfs
没有 \(return\) \(x\) - 未卜先知:输入过程中为了省时间,调用了还没有输入(即将输入)的东西
- 整型变量涉及除法时弄错优先级导致精度错误(eg. \(a*((b+1)/2)\) 指 \(a\) 乘上 \(b\) 除以 \(2\)向上取整,但如果将 \((b+1)/2\) 外层的括号去掉将导致错误),建议严格按照应有的想法使用括号
- 多个容易弄混的变量错用变量,需加强检查(如读程序)
- 前缀和的 \(l\) 没有减 \(1\)
- 多重循环中,各个循环中的变量名弄混、写错,如 \(f[i][j][k][l][o]\) 写成了 \(f[i][j][k][k][o]\)
- \(21\) 世纪最 \(sb\) 的 \(bug\) —— 输出顺序写错
memset
导致的玄学警告和出错。数组赋值尽量手写,尤其是赋值 \(-1\) 时- 写带模的运算时,写了形如
(a += xb + yc + ...) %= MOD
的代码,其中忘记了上面后面的等于号,即写了(a += xb + yc + ...) % MOD
。 - 如果你想知道一个
set
、map
的倒数第二个位置(一般用于获取次小值),你可以使用end()
然后运行两次--
,尽量(一定)不要使用rbegin()
,因为rbegin()
的++
才是正常的--
。 - 使用
1 << x
时,如果结果很大,一定要记得写1ll << x
,很容易错而且不好发现,最好的方法就是无论如何都写成1ll << x
。 - 在矩阵快速幂优化 dp 中,我们构造转移矩阵的时候,尽量使用加法,比如 \(i\) 可以转移到 \(j\),那么设置成 \(a_{i,j} + 1\),而不是直接赋值为 \(1\),这样可以避免重复转移但是被忽略的问题,可以参考 这道题 的 \(k = 1\) 的情况
- 在树上启发式合并的时候,我们有一个清空操作,也就是递归一个轻儿子后,会对这个儿子所在子树的贡献清空。如果有多颗树进行 dsu,就会导致递归完一个子树后,没有清空。可以建一个超级根节点,也可以递归完每一颗树的根后都调用一下清空函数。
- 函数中,后面还有内容要运行,前面却因为某些判断而
return
了。这种情况一般出现在,前面本来确实可以return
的,但是之后又在后面加了东西,或者写前面时忘了还要在后面写东西,写代码时留意就好,尽量不用return
这种效果很强的语句。 - 如果要判断 \(x\) 是不是完全平方数,可以这么写:
(int)sqrt(x) * (int)sqrt(x) == x
。需要注意的是sqrt
前面的(int)
不能缺少。
典 20 - 技巧
- 数学式子一般转换为适合计算、暴力求解的式子。
- 构造字典序最小的可行序列时,可以贪心考虑前几位最小,然后假设后几位为任意序列,只要可行即可,不需考虑后几位的顺序,然后再考虑后一位的顺序
- \(a^{x_1},a^{x_2},a^{x_3}\)\(\cdots\)\(a^{x_n}\) 时,注意递推式,不难,但容易出错
- \(a \% b = a - a / b * b\),其中 \(a/b\) 有可能相等,可以归为一个块集讨论
- \(jump\) \(jump\) \(jump\) \(jump\) \(jump\) \(!\) \(->\) 倍增
- 凯莱公式(Cayley‘s formula):
一个完全图有 \(n\) 个节点,那么它的生成树有 \(n^{n-2}\) 个
- 求多个数的两两异或和:
正常思路是暴力枚举,但是可以统计每一位上的 \(1\) 的个数和 \(0\) 的个数,然后相乘,就是这一位上能做贡献的数对的个数,然后再乘上这一位的贡献大小就行了(比如第3位,那就是再乘上 \(2^2\))
-
无向图判奇偶环:给每个点交替染色为黑、白两种颜色,然后根据下一个有色点和颜色判断产生了奇数环还是偶数环
-
启发式合并,好用嘞很
-
一些路径上的信息,可以转化为点之间的信息,尤其是与异或有关的(e.g.树上 \(a\) 到 \(b\) 的简单路径的异或和,可以是 \(s_a\) \(xor\) \(s_b\) \(xor\) \(a_{LCA_{(a,b)}}\),其中 \(s_i\) 是根节点到点 \(i\) 的简单路径的异或和,\(a_i\) 是点 \(i\) 的权值)
-
有些题目可以倒着理解。比如按照某种规则在集合中加入一些数,可以倒过来,先把所有数加入集合中,然后按照这种规则从集合中一个一个删除数,参考题目 CF1886D
-
一个序列内有很多数,要求前 \(k\) 小的所有数的和。如果值域较小,可以先用一些方法(如主席树)求出第 \(k\) 小的数,然后用一些方法(如分块)求出区间内小于这个数的所有数的和,以及有多少这样的数(记为 \(s\)),最后还剩下 \(k-s\) 个数,这些数的值都是第 \(k\) 小的数,加上即可,参考题目 P2468 [SDOI2010] 粟粟的书架
-
优秀的 hash 模数:\(1610612741\)。
-
枚举被二进制数 \(i\) 包含的所有二进制数 \(j\):
for(int j = i; j; --j &= i)
效果就是设 \(i\) 为 \(10010\),那么 \(j\) 就是 \(10000\)、\(00010\)、\(00000\),可以用于状压时枚举一个状态的子集(直接枚举所有集合并判断是否包含复杂度为 \(O(4^n)\),此方法为 \(O(3^n)\)) -
一个有向无环图中连有一些边,将这个图的拓扑序列出来,设 \(id_x\) 表示 \(x\) 在序列中的位置,如果有一边从 \(x\) 指向 \(y\),那么得到 \(id_x \le id_y\)。
-
有时卡常,可以手算一下范围,然后在 \(long\) \(long\) 可以接受的范围内少写一些取模运算,最后再集中取模。多用于 dp 转移式分类讨论比较多的,或是其他的做了很多无用的取模的运算(卡常效果非常好,特别是这种无用的取模做了很多次)。
-
计数类的题目,除了常规的组合数、组合模型、dp 之外,有一个很重要的技巧,拆贡献,拆开算,会好写很多,参考题目:USACO20FEB Help Yourself G。
-
连通块 dp,有时很好用。还有的题目的连通块可以转化为树上 dp。这两种题目可以分别参考 USACO20JAN Cave Paintings P 和 PERIODNI - Periodni。
-
如果我们支持的操作仅有交换字符串的两个相邻的字符,我们要把字符串从起始转化为目标,可以有一个套路,那就是把起始字符串每个字符附上标号,然后按照这样的标号带到目标字符串上,记为数组 \(a\),然后数组 \(a\) 的逆序对个数就是最少操作次数。
- 例如我们要把字符串 \(aaaza\) 转化为 \(azaaa\),我们先标号起始字符串,然后带到目标中,为了保证我们不会交换两个相同的字符,我们的数组 \(a\) 是 \(\{1,4,2,3,5\}\),因为这样的话四个 \(a\) 之间不会产生任何逆序对。然后我们直接求逆序对个数就可以了。
- 参考题目:E. String Reversal
-
对于欧几里得距离不同之类的限制,有一个方法就是判断奇偶(或平方后的奇偶),奇偶不同就一定不同,奇偶判断完了还可以扯上对一个数取模的结果,常用的就是比如对 \(4\) 取模,因为偶数加偶数取模后是 \(0\),奇数加偶数是 \(2\)。参考题目 Good Bye 2019 E. Divide Points。
-
求 LIS(最长上升子序列),不仅可以用常规的枚举最后一个数然后转移 dp,因为只能是小的转移到大的,所以其实还可以把所有数排序,然后从小到大更新他的 dp,维护数据结构每次取前缀最小值就行了。这一方法可以扩展到多种多样的 LIS 上,比如字符串(字串序列,排列用 Trie)。
-
对一颗无根树操作,当一个节点需要维护他的邻节点的信息时,可以考虑转化为有根树,这样的话,一个节点被操作后,只有他的父亲和他自己会受影响,参考题目:Chain Queries
-
求 \(1 \ xor \ 2 \ xor \cdots xor \ n\),有
-
有一种题型,允许你对一个序列进行多次一种操作,这个操作通常是选择一个区间然后对序列的这个区间进行操作,这时候通常满足一个东西,就是区间越大、限制越多、代价越小,比如区间异或上一个数,显然这个区间选的越小越多,操作的自由度就越高。这时存在一中特殊的代价,那就是区间长度除以二向上取整,这满足一个东西,区间长度为 \(1\),代价是 \(1\),区间长度是 \(2\),代价是 \(1\),区间长度大于 \(2\),必然不如拆成一堆 \(2\) 和一堆 \(1\),这时候就可以考虑仅仅使用长度为 \(1\)、\(2\) 的区间了,参考题目:A2. Burenka and Traditions (hard version)
-
当 \(x \& y = 0\),\(x\) 的逆在二进制下包含 \(y\);当 \(x | y = 11 \dots 1\),\(x\) 的逆在二进制下被包含于 \(y\)。
-
做题时关注对条件的转化,比如 01 串下,所有 \(1\) 之间的距离不能超过 \(k\),这就相当于每 \(k\) 段都必须包含至少一个 \(1\),这个东西同样可以扩展到字母域上,一个字符串每 \(k\) 个字母都要包含 \(c_1, c_2, \cdots, c_m\) 这些字母。参考题目:D. Cases。
-
对于条件涉及两两异或,特别是异或后小于、大于某个数之类的,总之涉及两两异或的,都可以考虑 01 trie,这是因为 01 trie 遍历到某个节点的时候,这个节点子树内所有的数,他们两两异或后都会把这个节点前面的位异或成 \(0\),所以只需要考虑子树内的东西即可,这时满足了 dp 的子结构,就可以对 01 trie dp,参考题目H. Keep XOR Low
-
循环矩阵是指,对于矩阵中的任意一项 \(a_{i,j}\),满足 \(a_{i, j} = a_{i - 1, j - 1}\),或 \(a_{i, 1} = a_{i - 1n}\),这样的话,只要知道循环矩阵中的第一行,就知道了整个矩阵。矩阵乘法中,如果相乘的两个矩阵是循环矩阵,那么相乘后得到的矩阵也是循环矩阵,这样的话,循环矩阵相乘的复杂度就是 \(O(n^2)\) 的。dp 中,转移矩阵很有可能是循环矩阵,这样的话就可以进行此优化了。
-
对于包含异或的东西,可以考虑,给定终止状态,看是否有唯一解,比如说,给定 \(01\) 序列,对于某些点你可以选择异或与否,问所有异或方案中有奇数个 \(1\) 的方案数。这道题是简单的,但反映了这个思想,还有一些题目也反映了这个思想,比如 这道题。
-
对于 \(f(i,j) = k, f(i,i) = i\)(\(i,j,k\) 互不相同)的函数,我们可以认定 \(i,j,k\) 分别为 \(0,1,2\),那么得到 \(f(x,y) = 3 - x - y \pmod 3\),利用这个性质,可以做一些题目。
-
对于一个排列,他最有代表性的,或是最特殊的点,之一,就是他最大值的位置,假设 \(f_n\) 表示长度为 \(n\) 的排列的个数,转移的时候就可以枚举他的最大值的位置,做到 \(O(n)\) 转移。显然,在排列个数这个问题中这样做并不优,但如果对排列有限制,同时限制和最大值、区间最大值有关,就可以这样写。参考题目。
典 30 - 随笔
- 12.9
状压相关的玩意儿,枚举状态的时候不要用for
循环里面不要用i
、j
这些变量,尽量用now
这些变量,防止混淆,导致 RE 上天。
- 12.9
某些算法(比如分层图、匈牙利算法、线段树),数组一定要开大,该大几倍大几倍,忘开就是挂。
- 12.9
做分讨有助于自我折磨,
做大模拟等于慢性自杀,
我与抽象大dp不共戴天。
- 12.13
有的时候啊,这个构造题,就像个大模拟似的,唯一不同就是要自己想策略,所以一定不要把构造题的代码写得很简洁、精妙、高级,就一步一步来,一步一步模拟,按照自己的策略慢慢来,要不然就是挂上天。
- 12.14
用网络流跑二分图要开双倍空间,别忘了。
- 12.16
在 hash 中,如果要把两个 hash 值对齐,最直观的方法当然是除一个数,但是这样子就要求逆元,也就是徒增一个 log。可以向后对齐,找一个很大的数,设为 MA,然后乘上 \(p^{MA-size}\),对齐到 MA 就可以减少一个 log。
- 12.20
链表不是高级数据结构,很低级,很多时候处理完一个节点的左右指针,这个节点会留下一堆错误的信息,但是有的信息很正确,导致程序误判,但是一般这个节点留下的信息是否正确可以根据题意较轻松的判断,所以只需要留意这一点,并且考虑周全即可。
- 1.26
字典树可以用来求一些字符串的排名(某个字符串在其中的排名),因为字典树某一个节点会对别的节点连边,假设查询的字符串的下一个字符是 \(c\),那就判断当前节点会不会对 \(a\) 和 \(b\) 连边,如果都不会,那这个字符串当前就是最小的。在此基础上,也可以按照题意完成“畸形”排序,参考题目:P3065 [USACO12DEC] First! G。