算法复习
省选前最后一周了,对照大纲过一遍,每个算法稍微写一点自己的理解与板子记忆技巧。
感觉很多东西还是之前听别人讲的时候学的似懂非懂......导致现在看起来好像啥都会一点实际上好像啥也不会,每次越临近考试就会感觉整个人很虚无啊......反正不是很好受。
主要写一些我不太会的,所以没啥参考意义。
数论
把不熟的板子题写一遍,exLucas 学一下。
- exgcd【7】
用于解决形如 \(ax+by=c\) 的整数域不定方程,有解的条件是 \(\gcd(a,b)|c\)(裴蜀定理 【7】)。
具体地,对于方程 \(ax+by=c\),辗转相除构造 \(a=b',b=a \bmod b\) 的解 \((x',y')\),那么 \(x=y'\),\(y=x'-\lfloor\frac{a}{b}\rfloor y'\),边界条件 \(b=0\) 时 \(x=1,y=0\),容易验证其正确性。
得到是一组特解,如果需要求最小最大解 / 正整数解之类的其他要求,直接调整即可。所有解和特解的关系形如 \(x=x_0+kb/g,y=y_0-ka/g\),其中 \(g=\gcd(a,b)\)。
void exgcd(ll a,ll b,ll&x,ll&y){
if(!b)return x=1,y=0,void();
exgcd(b,a%b,y,x);
y-=a/b*x;
}
- 费马小定理 【7】
\(a^{p-1}\equiv 1\pmod p\),\(p\) 是质数,常用于求模质数意义下的逆元。
- 威尔逊定理 【7】
\((p-1)!\equiv -1\pmod p\),证明考虑两两分组乘起来。
- 欧拉定理和欧拉函数 【7】
欧拉函数 \(\varphi(n)\) 表示 \([1,n]\) 中与 \(n\) 互质的正整数个数,是积性函数,可以线性筛求。
常用结论:\(\sum\limits_{d|n}\varphi(d)=n\),证明考虑组合意义。
(扩展)欧拉定理:当 \(b\ge \varphi(p)\) 时 \(a^b\equiv a^{b\bmod \varphi(p)+\varphi(p)}\pmod p\)(欧拉降幂法)
- exCRT 【7?】
exCRT 可以完全替代 CRT。
要求形如 \(x\equiv a_i \pmod {p_i}\) 的方程组。
考虑依次合并各个方程组,那么现在问题变为,已知前 \(i-1\) 个方程组的某个解为 \(x\),前 \(i-1\) 个 \(p\) 的 LCM 为 \(M\),那么调整这个 \(x\),加上 \(M\) 的倍数使得 \(x\) 满足第 \(i\) 个方程组。用形式化的语言描述就是 \(x+Mt\equiv a_i\pmod{p_i}\),移项得到 \(Mt\equiv a_i-x\pmod{p_i}\),可以表示为 \(Mt+p_is=a_i-x\) 的二元一次不定方程的形式,用 exgcd 解决。
- 原根和指数 【8】
阶:对于 \(a,m\),\(a\) 在 \(\bmod m\) 意义下的阶定义为最小的 \(p\) 满足 \(a^p\equiv 1\pmod m\)。
原根:阶为 \(\varphi(m)\) 的 \(a\)。
如果求得了一个原根 \(a\),那么可以得到所有原根 \(a^k\),其中 \(\gcd(k,\varphi(m))=1\),所以如果一个数有原根,个数一定是 \(\varphi(\varphi(m))\)。
\(2,4\) 和形如 \(p^{k},2p^{k}\)(其中 \(p\) 是奇质数)的数有原根。
检验一个数 \(x\) 是否是原根,需要满足 \(x^{\varphi(m)}=1\),且 \(x^{\varphi(m)/p}\neq 1\),其中 \(p\) 为 \(\varphi(m)\) 的质因子。
最小原根是 \(\mathcal O(n^{1/4})\) 级别的。
常用技巧:将乘法问题通过求原根的离散对数转化为加法问题。
- exBSGS 【8】
主要思想:meet in the middle。
目标:求解 \(a^x\equiv b\pmod p\)。
设 \(t=\sqrt(2\varphi(p))\),那么预处理出 \(a^{kt}\) 和 \(a^{k}\)(\(k\in [0,t]\))。
设 \(x=ft-g\),那么枚举 \(g\le t\),一个必要条件是 \(a^{ft}\equiv ba^g \pmod p\),找到对应的 \(f,g\) 中的前两个,带回检验(条件不是充分的)。只需要代入前两个是因为循环成 \(\rho\) 形,只有前两次有效。
存储使用 unordered_map 或者 gp_hash_table。
顺便 mark 一下这个 pbds 咋用:需要引入头文件 #include<ext/pb_ds/assoc_container.hpp>,并 using namespace __gnu_pbds,剩下和 unordered_map 一样用就行了。
- exLucas 【?】
求 \(\binom{n}{m}\bmod p\),\(n,m\le 10^{18},p\le 10^6\)。
将 \(p\) 分解为若干个形如 \(p^k\) 的子问题,最后用 exCRT 合并,下面考虑一个 \(p^k\) 的求解方式。
不能方便地计算 \(\binom{n}{m}\) 的问题主要在于包含质因子 \(p\) 的数不能求逆元,所以不妨先把所有的 \(p\) 分解出来。记 \(c(x)\) 为 \(x\) 包含的 \(p\) 因子个数,可以 \(\mathcal O(\log n)\) 求出一个 \(c(x)\)。现在要做的就是计算形如 \(\frac{n!}{p^{c(n!)}}\) 的事情,最后答案乘上多出来的 \(p\) 因子。设 \(f(n)\) 为如上这件事情,那么可以分治到规模为 \(n/p\) 的子问题,要乘上 \(n/p^k\) 个 \([1,p^k]\) 中非 \(p\) 倍数的数的乘积,这件事可以预处理。现在就可以 exgcd 求逆元了,复杂度 \(\mathcal O(p\log p)\)。
-
莫比乌斯反演 【9】
-
二次剩余 【10】
可能是你大纲最简单的 10 级,可以会一下。
目标:求解 \(x^2\equiv n\pmod p\)。
定义勒让德符号 \((n/p)\) 为:\(n\) 在 \(\bmod p\) 意义下为二次剩余时 \(=1\),\(n\equiv 0\pmod p\) 时等于 \(0\),否则等于 \(-1\)。
判断勒让德符号:当 \(p\) 为奇质数时,\(n^{(p-1)/2}\equiv 1\pmod p\) 说明 \(n\) 是二次剩余。
求解二次剩余运用 Cipolla 算法。首先,随机出一个 \(t\) 满足 \(t^2-n\) 不是二次剩余,设 \(w^2\equiv t^2-n\pmod p\)(虚数单位),那么可以构造 \((n+w)^{(p+1)/2}\) 为合法的二次剩余。
二次剩余相关的一些性质(\(p\) 为奇质数):两两配对和为 \(p\),\([1,p-1]\) 中恰好一半是二次剩余。
数据结构
把板子过一遍,重点是平衡树。
- 平衡树 【8】
熟练写的只有 fhq treap,虽然可能因为一个月没写过平衡树题了又变得不熟练了,不讲了。这几天打两遍板子。
- 可合并堆 【8】
pbds 启动。
- 虚树 【10】
虚假的十级算法。
一个简单的虚树建法:把关键点拿出来按照 dfs 序排序,将相邻的 lca 加入后再次排序并去重,此时 \(LCA(a_{i-1},a_i)\) 就是 \(a_i\) 在虚树上的父亲,比那个栈方法不知道高到哪里去了。
记得写一道没写过的板子题,比如消耗战。
图论
- tarjan:强连通,边双,点双,割点,割边,圆方树 【7】
强连通:记录 dfs 栈,遍历结束后 low[x]==dfn[x] 判断。
边双:找割边,对于 \((x,y)\),如果 low[y]>dfn[x] 说明回不去,是割边。边双只要把割边断开搜一遍就行了。
割点:对于非根节点,只要存在一个儿子能回到的最高点在 \(x\) 子树内 low[y]>=dfn[x] 就说明 \(x\) 是割点,对于根节点需要判断儿子个数 \(\ge 2\)。
点双:和找割点类似,每次出现 low[y]>=dfn[x] 就不断弹出搜索栈中的节点,直到出现 \(y\),并把 \(x\) 也加入这个分量里,注意特判孤立点。
圆方树:每个点双往一个方点连边,连成菊花就行了。
还有一个玩意儿叫做 Kosaraju,可以用 bitset 优化。我会背板子,牛牛牛。
没想到 NOI2023 后的半年居然能一遍全部写对。
-
次小生成树 【7】
-
次短路 【7】
-
欧拉路 【6】
H 开头忘了叫啥算法了。反正一路 dfs,记一下当前弧,最后倒着输出就行了。注意判无解。
如果用前向星写的话,注意字典序的排序应该是反的。
- 2-SAT 【8】
一般会拆点 \((x,0)\) \((x,1)\) 表示选 \(0/1\),那么 \(x=i\) 必须推出 \(y=j\) 就可以写成 \((x,i)\) 向 \((y,j)\) 连边的形式,如果 \((x,0)(x,1)\) 在同一个强连通分量里就无解。
构造方案的时候如果 \((x,0)\) 的强连通分量编号小于 \((x,1)\) 那么取 \(0\),否则取 \(1\)。
- 网络流 【8】
板子我会打,真牛。
上下界,不太会。
- 匈牙利算法 【8】
板子我会打,真牛。
好像打的不太好,抽空重打。
- KM 算法 【9】
板子我不会打,真不牛。
数学
- 高斯消元法 【7】
板子不太会打,难受了。
现在会了。舒服了。
- 群论,burnside,polya 【9】
这个可以先开摆。
-
Stirling 数 【9】
-
Prufer 序列 【9】
-
行列式,LGV 【9】
板子不太会打,难受了。
-
向量空间与线性相关 【9】
-
快速傅里叶变换 【10】
-
SG 函数 【9】
sg 函数的定义是其所有后继状态的 sg 函数的 mex,其中无出边的状态 sg 值为 \(0\)。
多重游戏的结论是:所有状态 sg 异或和 \(\neq 0\) 先手胜。
- 计算几何 【摆了】
字符串
- KMP 【5】
板子我会打,牛牛。
- Manacher 【8】
考虑实时维护当前所有回文串中右端点最靠右的,设其回文中心为 \(id\),右端点为 \(r\)。
那么计算 \(i\) 的回文半径的时候可以直接从 \(\min(r-i+1,R_{2\times id-i})\) 开始枚举(因为 \([id-r+1,id+r-1]\) 是回文的,所以 \(i\) 的问题可以到 \(i\) 的对称点记录,并且不能超过最大的长度),然后枚举就是均摊线性的了。
注意添加分隔符以计算长度为偶数的回文串并避免越界。
-
SA 【8】
-
ACAM 【8】
板子在 bfs 建 fail 树的时候两处都是 ch[fail[u]][i],总是写错,有点难受。
问题可以试着放到 fail 树上看。
求出现次数可以认为是求作为每个前缀的后缀次数。比如说求 \(t\) 在 \(s\) 中的出现次数,可以先找到 \(s\) 的前缀状态 \(s_1,\dots s_n\),那么出现次数就是 \(t\) 的子树内 \(s\) 状态的个数。
-
exKMP 【9】
-
SAM 【10】
杂项
-
A,IDA 【7】
-
dp 的各种优化方法
-
pbds 的使用方法
-
lg / loj 上的一些其他模板。
浙公网安备 33010602011771号