「批注」大纲
去年写的那个 trick 真的成小丑了感觉。
推荐大佬的总结:1 C202044zxy Alex_Wei。
时隔 1 年,我不知道和去年相比,这篇批注是否会更加清楚透彻简洁。
鉴于个人能力与未来发展方向,故只写 8 级以下知识点。
感觉还是有一点乱。并且有些东西大纲是没有的,后面还要补上。
dp 和贪心可能会单独写。dp 参考。
last upd on: 10.22 22:17
2.1 入门级
2.1.3 数据结构
- huffman 树:求最小权的树,权定义为所有点权值乘深度的和。
- 荷马史诗里面要求儿子不超过 \(k\),需要补 0 过后用堆贪心,每次选小的早一步合成作为更深点。
2.1.4 算法
3. 基础算法
-
贪心:
-
二分 & 三分:
- 对于判定性的二分,可以在 \(l,r\) 的边缘取常数个点进行判定,若失败则继续二分,用于卡常。
- 三分可以当 \(r-l\le k\) 的时候就停下来,最后再暴力跑 \([l,r]\) 的答案,避免被平台/精度卡。
-
倍增:
- st 表。注意
query
的时候分成的 2 个区间是有交的。这道题需要利用这一点。 - 给几个普通倍增的题:1 2:基环树 \(k\) 级祖先。对于每次操作方式相同的题要特别注意倍增。
- st 表。注意
4. 数值处理算法 高精度
一般来讲 __int128
就够了。
5. 排序算法
-
冒泡排序:考点较多。参考 this 以及 this,都是逆序对的转化以及考虑一个元素前面有多少个比他的大,记为 \(f_i\)。(一轮排序后 \(f_i\) 如果非 0 则改变 1)
-
计数排序:基本上没用。注意下面代码最后一行倒叙遍历是考虑情况:值相同但要保留输入顺序。
void counting_sort() {
for (int i = 1; i <= n; ++i) ++cnt[a[i]];
for (int i = 1; i <= w; ++i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; --i) b[cnt[a[i]]--] = a[i];
}
8. 动态规划
-
背包 dp:
-
区间 dp:
2.1.5 数学与其他
- 更相减损术的高级运用:P10463 Interval GCD。维护差分数组,就可以做到单点修改,区间查询 gcd。
2.2 提高级
2.2.2 C++ 程序设计
-
set
方面注意 ODT 的写法。Sample。split
是将一个段分开,并返回以 \(x\) 为开头的迭代器。update
中要先split
右边,erase
删除是左闭右开。如果你先query
再 upd 复杂度就是正确的。- 对于一般的题目随机数据下期望满分。可用于乱搞。
-
注意
map
可以优化某些状态数大,但可用状态不多的 dp。可以用于普通 dp 的乱搞。详见。
2.2.3 数据结构
1. 线性结构
-
st 表在
query
时分成的两个区间是相交的,可以根据这个东西处理一类 \(f(L,R)=\bigcup f(l_i,r_i)\),并要求 \((l_i,r_i)\) 相交的问题:CF1707E。 -
利用双栈维护双端插入删除,均摊复杂度线性。
2. 集合与森林
- 并查集可以维护序列里面一段连续的 0/1。
3. 特殊树
-
BIT 上二分的写法:记住 \(Sum(S)=Sum(S/\operatorname{lowbit(S)})\operatorname{ op } f(S)\)。从大到小枚举 bit 即可。Sample。
-
Trie 的全局加 1 操作:倒着建树,每次交换左右儿子,并递归到交换之后的 0 儿子。Sample:fusion tree。
-
笛卡尔树:注意构造方法,维护一条单减链。每次单调栈扫完之后建边。
- 树上 dp,给个例子。
-
平衡树:序列问题要维护相对顺序,使用文艺平衡树。注意 split 的引用参数写法。真题:列队。
- 以及和线段树类似的 pushup。放驼使。
5. 哈希表
-
字符串哈希:一般取单模数 \(10^9+7\)。
-
树哈希:找到重心,调用 xor shift 做和哈希。如果有两个重心就取哈希结果的 min。
-
和哈希/异或哈希:判断两个可重集是否相等,考虑 xor shift 的写法,注意为了防止被对着卡,mask 变量可以设计成随机数。
mt19937 myrand(chrono::steady_clock::now().time_since_epoch().count()); // 推荐种子
ull shift(ull x) {
x ^= mask;
x ^= x << 13; x ^= x >> 7; x ^= x << 17;
x ^= mask;
return x;
}
-
为了避免哈希冲突,对于有 2 个相关联的哈希的题目,应使其哈希方式不同。比如一个使用和随机数,另一个使用 xorshift 后进行累加。Sample。
-
某些题目转化哈希过后满足判定条件的解可能并不是我们最终希望的解,但若此情况出现概率极小,则不考虑。比如星战中我们要求所有 \(k_i=1\),但满足方程的 \(k\) 显然不止这一组,但是那样的 \(k\) 非常难构造(而且一般很大),于是忽略。
2.2.4 算法
3. 分治算法
-
某些矩阵计数的题可以考虑分治列,然后枚举上下行的边界,计算跨过 \(mid\) 的贡献。注意每次要对于跨度大的一维分治,这样分治总的复杂度才是 \(O(nm\log n)\)。
-
这道题是先考虑了 \(S=2\) 的情况,然后发现每次跨越 \(mid\) 时,每一列只需要指定匹配于另一列就一定可以通过后续调整有解。所以相当于是做 \(\log 2^k\) 次 \(S=2\) 的情况就等价到了 \(S=2^k\)。(注意很多时候题目都会提示 \(S=2^k\) 这种信息,一定要考虑到分治)
-
cdq:考虑左边对右边的贡献,很多时候为了方便计算,通常先计算左边,然后让左右各自内部排序,计算跨越 \(mid\) 的答案,然后再计算右边。
-
整体二分:算法思路是对询问和修改分治,把答案在 \(mid\) 两边的都分开。多数题目都是【求第 \(k\) 大】,这时要把小的那边的询问减去大的那边的个数。可以看道基础题。
5. 字符串相关算法 KMP only
-
建议频繁复习。筛选了好久选出来的讲解。本质上是一旦失配就一直向前跳 border。
-
Tips:能用 hash 的时候尽量用,最后才想 Kmp,比如这道题直接哈希加暴力就过了。(谁不更喜欢思维量更小的做法?)
-
bitset 在字符串匹配里的妙用。这玩意好写好用到离谱。
-
border 理论:推
-
(后面看到还有一些 8 级的字符串算法,暂时可能不会总结,如果我后面写了会把这句话删掉的。)
6. 搜索算法
-
A*:设计估价函数 \(H(i)\) 表示最优情况下 \(i\) 状态到终点的花费,若此时已经超过限制/劣于当前最优解,那么不必再搜索。
- 可用优先队列维护 \(F(i)+H(i)\),每次取最优的更新,保证第一次可以找到最优解。可能的应用:K 短路。
-
如果你写暴力/搜索,时刻注意能否记忆化,说不定会多很多分。
7. 图论算法
-
二分图:一些拓展。
- 判定有两种方式:1 是 dfs 点染色;还有 2:扩展域并查集(线段树分治会用)。
- 匈牙利匹配的复杂度是 \(O(nm)\),多数情况都不满。给个比较综合的题。
- 某些图论题可以考虑 二分图 和 奇环 的时候分别怎么做,最后拼在一起即可。
-
欧拉回路:《当看到图论题没有思路时,想想欧拉回路》
- 欧拉回路构造方法:当前弧优化,dfs 完后入栈。不涉及构造,但涉及思想。
- 一类套路题是把问题转化成图论模型后,要求把所有边重定向,使得每个点入度和出度相同。此时可以考虑欧拉回路,记录每条边是正是反。例题:1 2 3。
- 有时要求入度和出度相差不超过 1,可以加入一个超级源点,连向所有奇点(只有偶数个)。
-
有向无环图(DAG):
- 可以反向边 topo dp。
- 可以状压枚举子集给图分层 / dp。
-
双连通图:注意 tarjan 的写法。下面主要是记录一些结论,也可能有强连通分量。
-
最小生成树:
-
最短路:
-
LCA:几种求法和代码。维护路径简单信息:倍增;复杂信息:树剖;要求快速查询:欧拉序。
-
树的重心、直径:
-
dsu on tree。
8. 动态规划
-
树形 dp / 背包:
- 树上背包的平方写法。
- 换根 dp。
- 某些 dp 例如:\(dp_{x,0}=\sum dp_{to,1} \prod_{v\neq to} (dp_v+1)\)。要乘上逆元,但是无法处理除数为 0 的情况。此时可以需要维护前后缀积。
- 可能有时候需要考虑是否随便选 1 个根就可以计算所有答案了。
- 树上连通块背包:很多情况都是要子树合并,通常复杂度是 \(O(m^2)\)。此时我们按照 dfn 的倒序,每次对 \(f_i\) 加入单点:\((f_{i+1},I(i))\) 或者 \(f(r_i+1)\)。其中 \(r_i\) 表示 \(i\) 的子树内最大的 dfn。每次单点加入的复杂度就是 \(O(m)\) 了。
- 树上依赖背包:每次合并子树的时候强制 \(x\) 选物品即可(子树任意),这样就保证了如果一个数选了那么它到祖先的链上都必然选。Sample。
-
状压 dp:
-
dp 优化:
2.2.5 数学与其他
原谅我数学太菜,所以可能会写一些 sb 的东西。
2. 初等数论
-
欧拉定理:若 \(\gcd(a, m) = 1\),则 \(a^{\varphi(m)} \equiv 1 \pmod m\)。
-
欧拉函数:注意欧拉反演:\(n=\sum_{d|n} \varphi(d)\)。应用。
-
威尔逊定理:任意 \(p\),都有 \((p-1)!\equiv -1 \pmod p\)。应用。
-
裴蜀定理:设 \(a_1, a_2, \dots, a_n\) 均是不全为零的整数,若存在整数 \(x_1, x_2, \dots, x_n\), 使得 \(a_1 x_1 + a_2 x_2 + \cdots + a_n x_n=d\),则 \(d = \gcd(a_1, a_2, \dots, a_n)\)。应用。
-
扩展欧几里得算法:解方程 \(ax+by=1(a\bot b=1)\)。也可以推广到解 \(ax+by=c\),但必须 \(\gcd(a,b)|c\)。
- 该算法求出的解满足:\(|x|+|y|\) 在所有解中最小。
- 可用于求解逆元 \(ax\equiv 1\pmod p\),故要求 \(a\bot p\)。
-
中国剩余定理。
3. 离散与组合数学
-
组合、排列:
- 圆排列
- 盒子与球:
- 卡特兰(Catalan)数:
-
容斥:
-
斯特林(stiring)数:
4. 线性代数
-
矩阵:
- 邻接矩阵自乘 \(k\) 次方就是图上恰好走 \(k\) 的到达情况。
- 很多时候可以加入一个一维向量来降低矩乘的复杂度。
- 判断矩阵 \(A\) 和 \(B\) 相乘是否等于 \(C\)。我们引入随机一位向量先和一个矩阵相乘进行行列压缩,这样 \(A,B\) 再相乘就是平方。正确性见题解。
- 很多时候事件是分段的,我们可以对每一段都做矩阵乘法。Sample 1 2。
- 某些题并不支持 \(O(L^3)\) 的运算,此时我们需要式子变形使 \(L\) 变小。一种方法就是把矩阵乘法根据结合律改变乘法的顺序。
-
高斯消元:
-
线性基:
2.3 NOI 级
2.3.2 数据结构
3. 复杂树
-
树剖
- 维护每条重链上前后缀和和整体和。
- 对于树上路径问题,可以提前把链拉出来,转化为序列问题。Sample。
-
长剖
5. 可持久化数据结构
-
主席树:
- 在树上可以按照深度 / dfn 序建。
- 标记永久化:可持久化过后不支持
pushdown
操作,所以我们不下传操作;在询问的时候一路累加标记即可。参考。
-
可持久化并查集。本质是可持久化数组。某些时候可以和 kruskal 重构树互换。
2.3.3 算法
1. 算法策略
-
分块:
-
值域分块(治):以阈值 \(B\) 为分界,设置不同复杂度的算法,然后结合。可以理解为把 2 个暴力拼起来。Sample1 2 3。
- 狭义:对每个块维护整块的信息,查询跳整块,满足条件再跳单点即可。和莫队结合。
- 一个总长度为 \(L\) 的序列集合里,本质不同的长度只有 \(O(\sqrt L)\) 个。字符串同理。
- 专题:根号分治在背包上的运用。其实不只是背包,只是背包问题有启发性。
-
数论分块:
-
序列分块:
- 根号重构。
-
-
离线处理思想:
-
莫队:
- 带修莫队:
- 回滚莫队:
- 决策单调性里面也可以用类似莫队指针的移动来计算代价,均摊线性。
-
扫描线:
- 一种是维护所有 \(x\le r\) 的点的信息(离线树状数组),另一种是只维护 \(x=r\) 时的 \(y\)(经典线段树扫描线)。
-
-
复杂分治思想:线段树分治。
-
构造:
- 极力推荐。Ad-hoc 题目总结。
- 对于多个位置的还原问题,想想 3 个怎么做,然后延伸到 \(n\) 个。(一般情况下 2 个是不行的)Sample 1 2。
- 多打表。在小数据的多种构造方式中选择最容易推广的那一种。Samlpe。
3. 图论算法
-
基环树:总结。
- 找环建议直接 dfs。
- 核心就是先想树怎么做,然后再想环,最后再想树 + 环怎么做。
- 某些题还可以考虑删掉环上某一条边,变成树的问题。但是复杂度平方。Joker。
-
2-SAT:
- 线性做法:注意解的构造根据 \(i\) 和 \(inv(i)\) 中谁 scc 编号更小输出。(scc 大小是逆 topo 序,scc 大的先被遍历)
- 平方的 dfs 做法:可以用于构造满足字典序限制的解。这道题做法就是枚举前面几位相同,再判断后面随便填是否合法,当然还是要尽量选择小的。(注意这里平方都指 \(O(nm)\))