此文档仅为补充,这里就放一个大致目录和一些核心内容的说明(有些英文说太麻烦),可以在我的 cnblog 里找到很多核心代码的原理

文档

  • 使用:编译器需支持 C++17,强烈建议开启 O2 优化
  • 分类:数学,数据结构,字符串,图论,几何,杂类
  • 约定:以 S 为后缀的算法都是 慢且简单 的算法,?表示暂未实现。
  • 下标:默认以 0 开头,目前只有树状数组和一些树算法为了方便起见从 1 开头。

思想

  • 动态规划
  • 二分
  • 倍增
  • 分块
  • 分治
  • 水涨船高
  • 决策单调性优化
  • Meet in Middle
  • Small to Large

数学

基础模块:primary.hpp

  • 模快速幂
  • 向上取整和向下取整
  • int128 读写(快读,int, long long 也可以使用)
  • 二进制快速 gcd,拓展 gcd
  • 中国剩余定理 CRT
  • 常规二项式系数和模二项式系数(单例)
  • Lagrange 插值
  • 模自然数方幂和 算法
  • 矩阵乘法类(缓存优化)
  • MEX(集合中不出现的最小的自然数)
  • 快速排序(没带随机选择,别用)
  • 预处理最小素因子
  • BerlekampMassey(用来找最短递推公式)

BitCount

__builtin_popcount 更快的做法

bitCountTable 查表法很快,但是第一次会很慢,因此 __builtin_popcount 并未采用此方法

int bitCountTable(unsigned n) { 
  static int table[256] =  { 
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
    2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
    3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, 
  }; 
  return table[n & 0xff] + table[(n >> 8) & 0xff] +
        table[(n >> 16) & 0xff] + table[n >> 24];
}

__builtin_popcount 我猜测实现为如下(实测运行时间一致)

int bitCount0(unsigned x) {
  static const unsigned mask = 0x01010101;
  return  ((mask & x) + (mask & (x >> 1))
  + (mask & (x >> 2)) + (mask & (x >> 3))
  + (mask & (x >> 4)) + (mask & (x >> 5))
  + (mask & (x >> 6)) + (mask & (x >> 7))) % 255;
}

也就是说每隔 8 个 bit 一个 1,然后一个个的加上去(分成 4 组),最后这 4 组的答案加起来等价于模 255

注意到其实对于 unsigned,结果的最大值为 32,因此我们可以用 6 个bit 就能存下所有的答案,于是我们其实可以每隔 6 个 bit 一个 1(所以用 8 进制更合理),然后同样一个个的加(分成 6 组),最后这 6 组加起来等价于模 63

int bitCount1(unsigned x) {
  static const unsigned mask = 010101010101;
  return  ((mask & x) + (mask & (x >> 1))
  + (mask & (x >> 2)) + (mask & (x >> 3))
  + (mask & (x >> 4)) + (mask & (x >> 5))) % 63;
}

但其实可以更近一步,我们可以先 3 个 bit 一个 1,然后把 2 个 3bit 合成一个 6 bit

int bitCount2(unsigned x) {
  static const unsigned mask = 011111111111;
  unsigned tmp = (mask & x) + (mask & (x >> 1)) + (mask & (x >> 2));
  return  ((tmp + (tmp >> 3)) & 030707070707) % 63;
}

然后可以再近一步,注意到 , 所以 ,这样可以省一次 & 操作

int bitCount(unsigned n) {
  unsigned tmp = n - ((n >> 1) & 033333333333) - ((n >> 2) & 011111111111);
  return ((tmp + (tmp >> 3)) & 030707070707) % 63;
}

当然了我们还需要处理 unsigned long long,做法同理,对于 Table 法可以选取更大的 table,或者直接套用两次 bitCountTable,而对于 bitCount 此时就应该选择 4/8 bit 一次的做法了

int bitCountTableLL(unsigned long long n) {
  return bitCountTable(n >> 32) + bitCountTable(n & 0xffffffff);
}
int bitCountll(unsigned long long n) {
  unsigned long long tmp = n - ((n >> 1) & 0x7777777777777777ULL)
                            - ((n >> 2) & 0x3333333333333333ULL)
                            - ((n >> 3) & 0x1111111111111111ULL);
  return ((tmp + (tmp >> 4)) & 0x0f0f0f0f0f0f0f0fULL) % 255;
}

mod.hpp

  • MInt: 这个是类模板
  • ModInt
  • ModLL

FFT.hpp

NTT.hpp

FMT.hpp

快速 Mobius 变换,叫这个名字是因为跟数论函数的 Mobius 变换形式上一致

初等数论:numberTheory.hpp

  • 快速 素数筛和慢速线性筛
  • 快速计算
  • 快速计算第 n 个素数(从 1 开始标号,p[1] = 2,p[0] 无意义)
  • Euler 线性筛
  • Mobius 线性筛
  • 拓展欧拉定理(结论十分简单,证明需要分解素因式)
  • min_25 筛 算法求 Euler 函数前缀和,Mobius(绝对值)前缀和(内含 整除分块)
  • 最小素因子线性筛
  • 预处理素因子个数(算重/不算重)
  • 素因子分解(算重/不算重)
  • 求原根
  • 大素数 Miller-Rabin 概率判别法
  • Pollard-Pho 大整数最大最小素因子分解
  • 模素数取 log(BabyStepGaintStep)
  • 模素数开根号
  • 模素数开根号 的 Cipolla 算法
  • 模偶素数幂开根号?
  • 模奇素数幂开根号?
  • 模任意数开根号(先因式分解,看作模素数方开根号,再 CRT 整合)?

BigInt

杂类

  • 快速暴力 个集合中选 个,二进制为 1 的表示选择
  • KnuthShuffle
  • uniformChoose
  • Fibonacci 数列
  • floorSum:
  • sumNum:
  • decInc: 每次可选择 n 减一 或 m 加一,使得 m 是 n 的倍数的最小次数
  • Gauss 消元法浮点数版
  • 模 Gauss 消元法
  • 线性规划之单纯形算法
  • 任意模数多项式乘法 的 Karatsuba 算法(包括并行版)
  • 线性规划
  • FirstInRange:求最小的 使得 。类似 exgcd 的处理:求最小非负整数 使得 等价于 ,注意到我们要始终保持 ,因此当 时需要特判一下。转化成

KnuthShuffle 和 uniformChoose 原理

我们从后往前搞(),假设当前要处理 位置,那么 位置前的位置不可能有交换的情况,即不可能 , 或者 , 这是因为一但某个位置被换到后面之后就不会再变化了。

注意到上面这一点,就可以发现 后面的每个位置每个数出现的概率均等,且对于 , 的概率为 ,为 的概率为

所以 步之后所有位置概率都为

根据上述推理,我们在 个数中等概率的选取不同的 m 个数,显然只需要搞 步,后面的 个数为答案。然后默认 ,所以用一个 map 保存所有 不一定为 的位置的值

多项式(多项式全家桶 已全部 AC)

  • 仅包含乘法的四大多项式底层基类分别为:PolyBaseNTT, PolyBaseMFT3(弃用,被后面两个淘汰了), PolyBaseMFT4, PolyBaseFFT
  • PolyBaseNTT:基于固定的 NTT-friendly(原根一般为 3)模数快速数论变化(看具体题目,一般为 998244353)
  • PolyBaseMFT3:基于三个固定的 NTT-friendly 且原根为 3 的三模数(469762049, 998244353, 1004535809),利用 crt 求解任意模数多项式乘法(已被淘汰,请勿使用)
  • PolyBaseMFT4:基于四个固定的 NTT-friendly 且原根为 3 的四模数(595591169, 645922817, 897581057, 998244353),利用 crt 求解任意模数多项式乘法
  • PolyBaseFFT:基于 FFT 求解任意模数多项式乘法(需要注意精度)
  • 通过模板继承拓展得到全面的多项式类 Poly (加减乘除余,转置乘法,求导,积分,指数,对数,求逆,开方,一点求值,多点求值,快速幂模,内积,一个首一多项式的次方模 先取对数乘以次数再取指数得到,三角函数,反三角函数),这个过程学到了很多东西
  • 多项式静态函数: 计算
  • 多项式静态函数: 阶常系数递推公式的第
  • 多项式静态函数:模自然数方幂和 得到前 个答案
  • 多项式静态函数:Lagrange 插值:先分治求 ,再求 处的多点求值,再分治即可。
  • 求阶乘 :基于多点求值 个点之后暴力
  • 求阶乘 :min_25 用点求点 个点之后暴力

无运算的多项式底层基类:PolyBase(standard 在取余时,特别重要不可省略)

使用准则

  • 多项式项数
  • 要是超了 int,那就只能用 ModLL 版本 4 模数 Poly
  • 否则,要是 不固定就用使用 ModInt 的 FFT 版 Poly
  • 否则,当 为固定的 NTT-friendly 素数时,使用 NTT 版 Poly
  • 否则,使用 MInt 的 FFT 版 Poly

极简版多项式模板(polyS)

由于多项式模板一直扩展,动则 1000+ 行,实在有点搞,所以就搞了一个极简版的。

4 次 FFT 原理

我们本来要求 ,然后我们把它拆成 ,那么

于是我们只需计算 即可,但是注意到 所以本来需要 5 次 FFT,现在只需要 4 次即可。

其实本质上,我们可以只做 3.5 次 FFT,因为 2 次 dft 我们可以得到 的 dft 值,然后我们最后只需 3 次实数版 idft 即可(算作 1.5 次)!所以总的来说是 3.5 次。但是实现的时候也没办法搞 0.5 次,可惜。

min_25 用点求点原理(其实可以用下降幂更简洁的处理)

学习资料:zzqsblog, bztMinamoto

我们令 然后 ,我们想要得到 的值。然后

现在假设我们已经得到了

一个 次多项式由它在 个不同点的取值唯一决定(多于 d + 1 个点也可以)

  1. 我们如何求

注意到 即可 计算出 前 个,最后一个直接暴力计算即可。

  1. 我们如何求

同样我们注意到 如果我们设 ,(那么很关键的一点 ,卧槽, 在模 p 意义下得到就可以了,而且肯定大于 d,否则矛盾!)那么问题就转化成如何根据一个 次多项式的值:

以及

我们不妨对于任意的给定的 ,先求出

注意到根据 Lagrange 插值多项式

注意到这里的卷积跟我们普通的卷积不一致,左边长度为 的多项式乘以右边长度为 的多项式,然后次数为 位是有效的。

  1. 不能写成除以阶乘的形式,因为 x 有可能很大。
  2. 为了保证 ,我们需要使用 Wilson 定理即

分治 FFT

已知

这个显然可以分治来做,其实用生成函数推理可知

下降幂与点值

设 n 次多项式 ,则

因此 ,反过来也一样 (注意这里可以简单的多点求值,可以求更多的点)

下降幂与连续点值有 的转化。而普通多项式跟连续点值却没有,可以认为普通多项式要的连续其实是类似 FFT 那样的连续。但是注意到以连续点求连续点有 的做法

Binom

这里的单例很秀的一点就是用了 const 引用,但是却不妨碍我修改它的值!这样的好处:

  • 对于 MInt<M> 直接初始化了,不用在 setMod
  • 对于 ModInt, ModLL 这些本来就要 setMod,那就给它调用 setMod 重新刷新 Binom 的值

源代码在换 mod 的时候会有 bug,于 2021-7-24 重构 Poly,通过继承 vector 方式而非 vector 变量的方式的时候发现了这个 bug 并修复了

注意事项:如果利用了 vector 这种结构,然后再用引用可能会因扩容而导致 RE,可以通过预先申请较大的内存的做法,此后不要随便用 .back() 这类不确定的调用

Lagrange 反演

,则

特别地,若 ,则

几何

  • 二维凸包
  • 旋转卡壳(彻底弄懂原理,之后应用此原理推广应用解决:https://www.luogu.com.cn/problem/P7164)
  • 分治法求平面最短距离
  • k 维偏序之 bitset 暴力优化
  • 四边形优化 DP

图论

  • dfs 序
  • Euler 序
  • 最近公共祖先 LCA
  • 最小生成树 Prim
  • 最小树形图 LiuZhu
  • 拓扑排序
  • Euler 路
  • Hamilton 路?(NPC 问题,下次一定)
  • 带路径 Floyd
  • 最短路 Dijkstra
  • 最短路 BellmaFord
  • 最短路 SPFA
  • 连通分量之 Kosaraju 缩点
  • 连通分量之 2-SAT
  • 割点割边
  • 有向图 S-T 最大流 Dinic
  • 有向图 S-T 最大流的最高标号预流推进算法(HLPP) 算法
  • 无向图全局最小割 StoerWagner 算法
  • 最小费用最大流(势能 Dijkstra)
  • 三元环计数

三元环计数上界

首先定向之后,每条边的出度不会超过 (妙啊),这是因为 - 原来不超过的必然不超过 - 原来超过的只会连度大于等于它的,这个个数不会超过

字符串

  • Trie 普通字符串版
  • Trie01 求两两异或最大值(可在线)
  • Trie01(FusionTree) 求异或和(支持修改,全局加 1)
  • 前缀函数
  • 基于前缀函数的 KMP 算法
  • 基于前缀函数求前缀出现次数
  • Z-函数
  • 基于 Z-函数的 KMP 算法
  • AC 自动机
  • 后缀数组计算的 O(N) 诱导排序 SA-IS 算法
  • 最小表示法
  • Lyndon 分解的 Duval 算法
  • 处理回文的 Manacher 算法

数据结构

  • 暴力枚举
  • 纠错码
  • 离散化
  • 并查集 (Disjoint Union Set)
  • 树状数组 (FenwickTree)
  • 线段树 (Segment Tree)
  • 可持续化线段树(Persistable Segment Tree)
  • 树状数组套线段树求动态区间第 k 小
  • 莫队(线段树一样都是通用的类型,具体问题具体写)
  • 最长(严格)递增子序列
  • 单调队列
  • 单调栈
  • 笛卡尔树
  • 三维偏序之陈丹琪分治
  • 第二分块差值版(在线算法,离线可节省空间)
  • 第二分块绝对值版(在线算法,可搞成带修改版本!)