OI & ACM 笔记:C - 数学知识
C - 数学知识 组合计数
生成函数
普通生成函数
普通生成函数(OGF):序列 \(\{a_n\}\) 的普通生成函数,定义为形式幂级数
OGF 相加减:设 \(\{a_n\}, \{b_n\}\) 的 OGF 分别为 \(F(x), G(x)\),则 \(F(x) \pm G(x)\) 为序列 \(\{a_n \pm b_n\}\) 的 OGF。
OGF 相乘(卷积):设 \(\{a_n\}, \{b_n\}\) 的 OGF 分别为 \(F(x), G(x)\),则 \(F(x)G(x)\) 为序列 \(\{\sum a_ib_{n - i}\}\) 的 OGF。
\(\{1, 1, 1\cdots\}\) OGF 的封闭形式:
\(\{1, p, p^2\cdots\}\) OGF 的封闭形式:
- 斐波那契数列 OGF 的封闭形式:
- 卡特兰数 OGF 的封闭形式:
指数生成函数
指数生成函数(EGF):序列 \(\{a_n\}\) 的指数生成函数,定义为形式幂级数
EGF 相加减:设 \(\{a_n\}, \{b_n\}\) 的 EGF 分别为 \(F(x), G(x)\),则 \(F(x) \pm G(x)\) 为序列 \(\{a_n \pm b_n\}\) 的 EGF。
EGF 相乘(卷积):设 \(\{a_n\}, \{b_n\}\) 的 EGF 分别为 \(F(x), G(x)\),则 \(F(x)G(x)\) 为序列 \(\{\sum \binom{n}{i} a_ib_{n - i}\}\) 的 EGF。
\(\{1, 1, 1\cdots\}\) EGF 的封闭形式:
\(\{1, p, p^2\cdots\}\) EGF 的封闭形式:
排列数
排列数 \(A_n^m\):从 \(n\) 个有标号元素中,取出 \(m\) 个元素排成一列的方案数。
阶乘展开式:
组合数
组合数 \(\binom{n}{m}\):从 \(n\) 个有标号元素中,取出 \(m\) 个元素组成集合的方案数。
阶乘展开式:\(n, m \in \Z\),\(n \geq m \geq 0\)
对称恒等式:\(n, m \in \Z\),\(n \geq 0\)
吸收 / 提取恒等式:\(m \in \Z\),\(m \neq 0\)
加法 / 归纳恒等式:\(m \in \Z\)
上指标反转:\(m \in \Z\)
三项式版恒等式:\(m, k \in \Z\)
二项式定理:\(n \in \Z\)
- \(x = 1\) 且 \(y = 1\):
- \(x = 1\) 且 \(y = z\):
- \(x = 1\) 且 \(y = -1\):
广义二项式定理:\(r \in \R\)
平行求和法:\(m \in \Z\)
上指标求和法:\(m \in \Z\),\(n \geq 0\)
范德蒙德卷积:\(m \in \Z\)
斜对角线求和法:\(n \in \Z\)
组合数带权和:\(n \in \Z\)
组合数带权平方和:\(n \in \Z\)
插板法:
- 将 \(n\) 个完全相同的元素分成 \(k\) 组,保证每组至少有一个元素。求方案数:
- 将 \(k - 1\) 块板,插入 \(n - 1\) 个缝隙中。
- 将 \(n\) 个完全相同的元素分成 \(k\) 组,允许组内元素为空。求方案数:
- 给 \(k\) 组分别补一个元素,保证每组至少有一个元素。转化为问题 1。
- 将 \(n\) 个完全相同的元素分成 \(k\) 组,保证第 \(i\) 组至少能分到 \(a_i\) 个元素,\(\sum a_i \leq n\)。求方案数:
- 给第 \(i\) 组补 \(a_i\) 个元素,保证第 \(i\) 组至少能分到 \(a_i\) 个元素。转化为问题 2。
Lucas 定理:对于质数 \(p\),有
- 当 \(p = 2\) 时,\(\binom{n}{m} \bmod 2 = \binom{\left\lfloor n / 2 \right\rfloor}{\left\lfloor m / 2 \right\rfloor}\binom{n \bmod 2}{m \bmod 2} \bmod 2\)。故 \(\binom{n}{m} \bmod 2 = 1\) 当且仅当 \(m\) 在二进制表示下是 \(n\) 的子集。
卡特兰数
卡特兰数 \(\mathrm{Cat}_n\):从原点 \((0, 0)\) 出发,每次向 \(x\) 轴或 \(y\) 轴正方向走,且不越过直线 \(y = x\),到达 \((n, n)\) 点的方案数。
卡特兰数通项公式:
推导:
- 不加以任何约束的情况下,方案数为 \(\binom{2n}{n}\)。
- 不越过直线 \(y = x\),相当于不触碰直线 \(y = x + 1\)。对于触碰直线 \(y = x + 1\) 的路径,将触碰到的第一个点以后的路径全都关于 \(y = x + 1\) 对称,得到一条从 \((0, 0)\) 出发到达 \((n - 1, n + 1)\) 的路径。由此,可以在「从 \((0, 0)\) 出发到达 \((n - 1, n + 1)\) 的路径集合」与「从 \((0, 0)\) 出发到达 \((n, n)\),且触碰直线 \(y = x + 1\) 的路径集合」之间建立双射。故不合法的方案数为 \(\binom{2n}{n - 1}\)。
卡特兰数递推公式:
卡特兰数问题:
- 从原点 \((0, 0)\) 出发,每次向 \(x\) 轴或 \(y\) 轴正方向走,且不越过直线 \(y = x\),到达 \((n, n)\) 点的方案数 \(\mathrm{Cat}_n\)。
- 以 \(1, 2, \cdots, n\) 为 \(n\) 个元素的进栈序列时,出栈序列的方案数 \(\mathrm{Cat}_n\)。
- 以 \(n\) 对括号组成的合法括号序列的方案数 \(\mathrm{Cat}_n\)。
- 以 \(n\) 个节点构造无标号、左右儿子区分的二叉树个数 \(\mathrm{Cat}_n\)。
- 以 \(n\) 个节点构造无标号、儿子区分的有根树 \(\mathrm{Cat}_{n - 1}\)。
- 以圆周上的 \(2n\) 个点为端点,连互不相交的 \(n\) 条弦的方案数 \(\mathrm{Cat}_n\)。
- 将凸 \(n\) 边形用其 \(n - 3\) 条对角线分割为互不重叠的 \(n - 2\) 个三角形的方案数 \(\mathrm{Cat}_{n - 2}\)。
- 将 \(1 \sim 2n\) 中的每个数不重不漏地填入一个 \(2 \times n\) 的矩阵,每个元素大于其左方、上方元素的方案数 \(\mathrm{Cat}_n\)。
斯特林数
上升幂与下降幂的互化:
第一类斯特林数
第一类斯特林数 \(\begin{bmatrix} n \\ m \end{bmatrix}\)(斯特林轮换数):将 \(n\) 个有标号的元素,划分成 \(m\) 个互不区分的非空轮换(圆排列)的方案数。
第一类斯特林数递推公式:
- 由递推公式得,\(\begin{bmatrix} n \\ m \end{bmatrix}\) 可以看作从 \(1, \cdots, n - 1\) 中选出 \(n - m\) 个数相乘后相加的结果。
第一类斯特林数的行之和:
第一类斯特林数与上升幂:
- 代数意义:\(\begin{bmatrix} n \\ i \end{bmatrix}\) 可以看作从 \(1, \cdots, n - 1\) 中选出 \(n - i\) 个数相乘后相加的结果,这与 \(x^i\) 系数的意义相同。
第一类斯特林数 · 行:
第二类斯特林数
第二类斯特林数 \(\begin{Bmatrix} n \\ m \end{Bmatrix}\)(斯特林子集数):将 \(n\) 个有标号的元素,划分成 \(m\) 个互不区分的非空子集的方案数。
第二类斯特林数递推公式:
第二类斯特林数与下降幂:
- 组合意义:等式两端均表示将 \(n\) 个带标号球放进 \(x\) 个带标号盒子的方案数。左式根据乘法原理直接计算;右式枚举了非空盒子数 \(i\),用有标号盒子方案数 \(x^{\underline{i}}\) 乘上有标号球方案数 \(\begin{Bmatrix} n \\ i \end{Bmatrix}\)。
下降幂乘组合数:
第二类斯特林数通项公式:
第二类斯特林数 · 行:注意到通项公式是一个卷积的形式,运用 NTT 即可。
贝尔数
贝尔数 \(B_n\):将 \(n\) 个两两不同的元素划分成若干个互不区分的非空子集的方案数。
贝尔数递推公式:
贝尔数和第二类斯特林数的关系:
分拆数
\(k\) 部分拆数 \(p_{n, k}\):将自然数 \(n\) 拆成 \(k\) 个非严格递减正整数和的方案数。
\(k\) 部分拆数递推公式:
\(k\) 部互异分拆数 \(pd_{n, k}\):将自然数 \(n\) 拆成 \(k\) 个严格递减正整数和的方案数。
\(k\) 部互异分拆数递推公式:
斐波那契数列
斐波那契数列递推公式:
斐波那契数列递推公式(矩阵形式):
斐波那契数列通项公式:
卡西尼性质:
附加性质:
广义斐波那契数列:对于 \(\mathrm{Fib}_n(n < 0)\),有 \(\mathrm{Fib}_n = \mathrm{Fib}_{n + 2} - \mathrm{Fib}_{n + 1}\),则
类斐波那契数列:设 \(G_n = G_{n - 1} + G_{n - 2}\),且 \(G_0 = a, G_1 = b\),则
斐波那契数列求和:
斐波那契数列平方和:
斐波那契数列相邻项互质性质:
斐波那契数列 \(\gcd\) 性质:
斐波那契数列 \(\gcd\) 附加性质:
斐波那契数列模意义循环节:
- 斐波那契数列 \(\mathrm{mod} \ m\) 的周期不大于 \(6m\)。
- 斐波那契数列 \(\mathrm{mod} \ 5\) 的最小正周期为 \(20\)。
- 斐波那契数列 \(\mathrm{mod} \ p\)(\(p\) 是质数且 \(p \equiv \pm1 \pmod 5\))的最小正周期是 \(p - 1\) 的因数。
- 斐波那契数列 \(\mathrm{mod} \ p\)(\(p\) 是质数且 \(p \equiv \pm2 \pmod 5\))的最小正周期是 \(2p + 2\) 的因数。
- 设斐波那契数列 \(\mathrm{mod} \ p\)(\(p\) 是质数)的最小正周期为 \(\mathrm{per}\),则 \(\bmod p^k\) 的最小正周期是 \(p^{k - 1} \mathrm{per}\) 的因数。
- 设斐波那契数列 \(\mathrm{mod} \ p_i^{c_i}\) 的最小正周期为 \(\mathrm{per}_i\),则 \(\mathrm{mod} \prod p_i^{c_i}\) 的最小正周期为 \(\mathrm{lcm}(\mathrm{per}_i)\)。
容斥原理
容斥原理(集合并):
容斥原理(集合交):
集合反演
集合反演(子集和):
集合反演(超集和):
二项式反演
二项式反演(形式 1):
二项式反演(形式 2):
斯特林反演
斯特林反演(形式 1):
斯特林反演(形式 2):
莫比乌斯反演
莫比乌斯容斥系数:互质二元组的贡献和,等于 \(\sum f(d)\mu(d)\)。其中 \(f(d)\) 表示二元组均为 \(d\) 的倍数时的贡献和。
莫比乌斯反演(形式 1):
莫比乌斯反演(形式 2):
min-max 反演
min-max 反演:
在期望背景下,min-max 容斥仍然成立
\[E(\max(S)) = \sum\limits_{T \subseteq S} (-1)^{|T| + 1} E(\min(T)) \\ E(\min(S)) = \sum\limits_{T \subseteq S} (-1)^{|T| + 1} E(\max(T)) \]
在唯一分解背景下,min-max 容斥仍然成立
\[\mathrm{lcm}(S) = \prod\limits_{T \subseteq S} \left(\gcd(T)\right)^{(-1)^{|T| + 1}}\\ \gcd(S) = \prod\limits_{T \subseteq S} \left(\mathrm{lcm}(T)\right)^{(-1)^{|T| + 1}} \]
kth min-max 反演:
在期望背景下,kth min-max 容斥仍然成立
\[E(\mathrm{kthmax}(S)) = \sum\limits_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1} E(\min(T)) \\ E(\mathrm{kthmin}(S)) = \sum\limits_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1} E(\max(T)) \\ \]
单位根反演
单位根反演:
C - 数学知识 多项式
多项式基础
多项式的度:对于一个多项式 \(f(x)\),其最高次项的次数称为改多项式的度,记作 \(\mathrm{deg} \ f\)。
多项式的界:对于一个多项式 \(f(x)\),我们通常只取其前 \(n\) 项方便进行计算,此时应记作 \(f(x) \bmod x^n\),以表示 \(f(x)\) 的 \(0\) 到 \(n - 1\) 次项组成的多项式。
多项式的乘法:对于两个多项式 \(f(x) = \sum_{i = 0}^n a_ix^i\) 与 \(g(x) = \sum_{i = 0}^m b_ix^i\),则多项式 \(Q(x) = f(x) \cdot g(x)\) 为
多项式的形式导数:
多项式的形式积分:
多项式的逆:对于一个多项式 \(f(x) = \sum_{i = 0}^n a_ix^i\),若常数项 \(a_0 \neq 0\),则存在其乘法逆元 \(f^{-1}\) 满足
多项式 \(n + 1\) 次加法与 \(n + 1\) 次乘法求值:
int func(int k) {
int ans = 0;
for (int i = c; i >= 0; i --) ans = (1ll * ans * k + f[i]) % mod;
return ans;
}
多项式的求值:给出一个多项式 \(f(x)\) 和 \(n\) 个点 \(x_1, x_2, \cdots, x_n\),求 \(f(x_1), f(x_2), \cdots, f(x_n)\)。
多项式的插值:给出 \(n + 1\) 个点 \((x_1, y_1), (x_2, y_2)\cdots, (x_{n + 1}, y_{n + 1})\),求一个 \(n\) 次多项式 \(f(x)\) 使得这 \(n + 1\) 都在 \(f(x)\) 上。
拉格朗日插值
拉格朗日插值:
求一个点值的时间复杂度为 \(\mathcal{O}(n^2)\)。
int qpow(int a, int b, int p) {
int ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = 1ll * ans * a % p;
a = 1ll * a * a % p;
}
return ans;
}
const int mod = 998244353;
int c;
int x[N], y[N];
int lagrange(int k) {
int ans = 0;
for (int i = 1; i <= c + 1; i ++) {
int p = 1, q = 1;
for (int j = 1; j <= c + 1; j ++) {
if (i == j) continue;
p = 1ll * p * (k - x[j]) % mod;
q = 1ll * q * (x[i] - x[j]) % mod;
}
ans = (ans + 1ll * p * qpow(q, mod - 2, mod) % mod * y[i]) % mod;
}
return (ans + mod) % mod;
}
拉格朗日插值求系数:
- 将系数部分提取出来,记 \(a_i = y_i\prod_{j \neq i} \frac{1}{x_i - x_j}\)。
- 将包含自变量 \(z\) 的部分提取出来,记 \(h_i(z) = \prod_{j \neq i} (z - x_j), g(z) = \prod (z - x_j)\)。
- 注意到 \((z - x_i)h_i(z) = g(z)\),则有 \([z^{j - 1}]h_i - x_i[z^j]h_i = [z^{j}]g\),即
求所有系数的时间复杂度为 \(\mathcal{O}(n^2)\)。
int qpow(int a, int b, int p) {
int ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = 1ll * ans * a % p;
a = 1ll * a * a % p;
}
return ans;
}
const int mod = 998244353;
int c;
int x[N], y[N];
int a[N], g[N], h[N];
int f[N];
void lagrange() {
for (int i = 1; i <= c + 1; i ++) {
int q = 1;
for (int j = 1; j <= c + 1; j ++) {
if (i == j) continue;
q = 1ll * q * (x[i] - x[j]) % mod;
}
if (q < 0) q += mod;
a[i] = 1ll * y[i] * qpow(q, mod - 2, mod) % mod;
}
g[0] = 1;
for (int i = 1; i <= c + 1; i ++) {
for (int j = i; j >= 1; j --) g[j] = (1ll * g[j] * (mod - x[i]) + g[j - 1]) % mod;
g[0] = 1ll * g[0] * (mod - x[i]) % mod;
}
for (int i = 1; i <= c + 1; i ++) {
int inv = qpow(mod - x[i], mod - 2, mod);
h[0] = 1ll * g[0] * inv % mod;
for (int j = 1; j <= c; j ++) h[j] = 1ll * (g[j] - h[j - 1] + mod) * inv % mod;
for (int j = 0; j <= c; j ++)
f[j] = (f[j] + 1ll * a[i] * h[j]) % mod;
}
}
int func(int k) {
int ans = 0;
for (int i = c; i >= 0; i --) ans = (1ll * ans * k + f[i]) % mod;
return ans;
}
横坐标为连续整数时的拉格朗日插值:
- 给出 \(n + 1\) 个点 \((1, f(1)), f(2, f(2)), \cdots, (n + 1, f(n + 1))\),带入拉格朗日插值公式,得
记 \(\mathrm{pre}_i = \prod_{1 \leq j \leq i} (x - j), \mathrm{suf}_i = \prod_{i \leq j \leq n + 1} (x - j)\),则
求一个点值的时间复杂度为 \(\mathcal{O}(n)\)。
- 若给出 \(n + 1\) 个点 \((h, f(h)), (1 + h, f(1 + h)), \cdots, (n + h, f(n + h))\),则可以考虑将坐标系向左平移 \(h - 1\)。
int qpow(int a, int b, int p) {
int ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = 1ll * ans * a % p;
a = 1ll * a * a % p;
}
return ans;
}
const int mod = 998244353;
void add(int &x, const int &y) {
x += y;
if (x >= mod) x -= mod;
}
void dec(int &x, const int &y) {
x -= y;
if (x < 0) x += mod;
}
int c;
int y[N];
int fact[N], facv[N];
void fact_init(const int &n) {
fact[0] = 1;
for (int i = 1; i <= n; i ++) fact[i] = 1ll * fact[i - 1] * i % mod;
facv[n] = qpow(fact[n], mod - 2, mod);
for (int i = n - 1; i >= 0; i --) facv[i] = 1ll * facv[i + 1] * (i + 1) % mod;
}
int pre[N], suf[N];
int lagrange(int k) {
if (k <= c + 1) return y[k];
pre[0] = 1;
for (int i = 1; i <= c + 1; i ++) pre[i] = 1ll * pre[i - 1] * (k - i) % mod;
suf[(c + 1) + 1] = 1;
for (int i = c + 1; i >= 1; i --) suf[i] = 1ll * suf[i + 1] * (k - i) % mod;
int ans = 0;
for (int i = 1; i <= c + 1; i ++) {
int value = 1ll * pre[i - 1] * suf[i + 1] % mod
* facv[i - 1] % mod * facv[c + 1 - i] % mod * y[i] % mod;
if (value < 0) value += mod;
(c + 1 - i) & 1 ? dec(ans, value) : add(ans, value);
}
return ans;
}
FFT
单位根的简单性质:
- \(\omega_{n}^i \times \omega_{n}^j = \omega_{n}^{i + j}\)。
- \(\omega_{2n}^{2k} = \omega_{n}^k\)。
- \(\omega_{2n}^k = -\omega_{2n}^{k + n}\)。
FFT 补充界:对于一个多项式,需要对其最高位补 \(0\),将其补成一个最高项次数为 \(2^k - 1(k \in \N^*)\) 的多项式。
FFT:
- 正变换 DFT:系数表示法转点值表示法。取自变量 \(\omega_n^0, \omega_n^1, \cdots, \omega_n^{n - 1}\),计算因变量 \(f(\omega_n^0), f(\omega_n^1), \cdots, f(\omega_n^{n - 1})\)。将多项式 \(f(x)\) 中的项按次数奇偶性分成两组,分别产生两个新的多项式 \(G(x)\) 与 \(H(x)\),则
进一步,有
- 逆变换 IDFT:点值表示法转系数表示法。结论:将 DFT 中的单位根 \(\omega_n^1\) 换成 \(\omega_n^{-1}\),并在完成时除以 \(n\)。
- DFT + IDFT 本质上求的是循环卷积,即
位逆序置换:模仿递归,将系数在原数组中拆分。一个位置经过变换后的位置,相当于将二进制表示进行翻转。
蝶形运算:使用位逆序置换后,对于给定的 \(n, k\)
- \(G(\omega_{n / 2}^k)\) 的值储存在下标为 \(k\) 的位置,\(H(\omega_{n / 2}^k)\) 的值储存在下标为 \(k + \frac{n}{2}\) 的位置。
- \(f(\omega_n^k)\) 的值储存在下标为 \(k\) 的位置,\(f(\omega_n^{k + n / 2})\) 的值储存在下标为 \(k + \frac{n}{2}\) 的位置。
const int SZ = N * 4;
const double pi = acos(-1);
struct comp {
double x, y;
comp() {}
comp(double A, double B) : x(A), y(B) {}
comp operator + (const comp &rhs) const {
return comp(x + rhs.x, y + rhs.y);
}
comp operator - (const comp &rhs) const {
return comp(x - rhs.x, y - rhs.y);
}
comp operator * (const comp &rhs) const {
return comp(x * rhs.x - y * rhs.y, x * rhs.y + y * rhs.x);
}
};
int L, P;
int rev[SZ];
void poly_init(const int &n) {
L = 1, P = 0;
while (L <= n) L <<= 1, P ++;
for (int i = 0; i < L; i ++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (P - 1));
}
void FFT(comp *a, const int &n, const int &opt) {
for (int i = 0; i < n; i ++)
if (i < rev[i]) std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1) {
comp omega = comp(cos(pi / k), opt * sin(pi / k));
for (int i = 0; i < n; i += (k << 1)) {
comp x = comp(1, 0);
for (int j = 0; j < k; j ++, x = x * omega) {
comp u = a[i + j], v = x * a[i + j + k];
a[i + j] = u + v, a[i + j + k] = u - v;
}
}
}
if (opt == -1)
for (int i = 0; i < n; i ++) a[i].x /= n;
}
NTT
原根的简单性质:在模 \(p\) 意义下,s用原根来代替单位根。设 \(n = 2^k(k \in \N^*)\),\(p\) 为素数且 \(n \mid (p - 1)\),\(g\) 为 \(p\) 的一个原根。设
则有
- \(g_{n}^i \times g_{n}^j = g_{n}^{i + j}\)。
- \(g_{2n}^{2k} = g_{n}^k\)。
- \(g_{2n}^k = -g_{2n}^{k + n}\)。
const int SZ = N * 4;
int L, P;
int rev[SZ];
void poly_init(const int &n) {
L = 1, P = 0;
while (L <= n) L <<= 1, P ++;
for (int i = 1; i < L; i ++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (P - 1));
}
void NTT(int *a, const int &n, const int &opt) {
for (int i = 1; i < n; i ++)
if (i < rev[i]) std::swap(a[i], a[rev[i]]);
int g = opt == 1 ? 3 : (mod + 1) / 3;
for (int k = 1; k < n; k <<= 1) {
int omega = qpow(g, (mod - 1) / (k << 1), mod);
for (int i = 0; i < n; i += (k << 1)) {
int x = 1;
for (int j = 0; j < k; j ++, x = 1ll * x * omega % mod) {
int u = a[i + j], v = 1ll * x * a[i + j + k] % mod;
add(a[i + j] = u, v), dec(a[i + j + k] = u, v);
}
}
}
if (opt == -1) {
int inv_n = qpow(n, mod - 2, mod);
for (int i = 0; i < n; i ++) a[i] = 1ll * a[i] * inv_n % mod;
}
}
常用 NTT 模数:
998244353
:原根为 \(3\)。924844033
:原根为 \(5\)。167772161
:原根为 \(3\)。
分治 FFT / NTT
- 运用 CDQ 分治,可解决一些半在线的卷积。在序列分治的过程中,取分治重心 \(\mathrm{mid} = \left\lfloor \frac{l + r}{2} \right\rfloor\),尝试分成以下几类讨论:
- (1)分治递归到叶子节点(即 \(l = r\)),确定 \(f_l\) 的值。
- (2)递归计算区间 \([l, \mathrm{mid}]\) 中的 \(f\) 值。
- (3)运用 FFT / NTT 计算区间 \([l, \mathrm{mid}]\) 中的 \(f\) 值对区间 \([\mathrm{mid} + 1, r]\) 中的 \(f\) 值的贡献(补充界 \(> r - l\))。
- (4)递归计算区间 \([\mathrm{mid} + 1, r]\) 中的 \(f\) 值。
const int SZ = N * 4;
int f[N], g[N];
int L, P;
int rev[SZ];
void poly_init(const int &n) {
L = 1, P = 0;
while (L <= n) L <<= 1, P ++;
for (int i = 1; i < L; i ++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (P - 1));
}
int omega_buc[SZ][2], inv_buc[SZ];
void NTT(int *a, const int &n, const int &opt) {
for (int i = 1; i < n; i ++)
if (i < rev[i]) std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1) {
int omega = omega_buc[k][opt == 1 ? 0 : 1];
for (int i = 0; i < n; i += (k << 1)) {
int w = 1;
for (int j = 0; j < k; j ++, w = 1ll * w * omega % mod) {
int u = a[i + j], v = 1ll * w * a[i + j + k] % mod;
add(a[i + j] = u, v), dec(a[i + j + k] = u, v);
}
}
}
if (opt == -1)
for (int i = 0; i < n; i ++) a[i] = 1ll * a[i] * inv_buc[n] % mod;
}
int tmp_f[SZ], tmp_g[SZ];
void solve(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
solve(l, mid);
poly_init(r - l);
for (int i = 0; i < L; i ++) tmp_f[i] = tmp_g[i] = 0;
for (int i = l; i <= mid; i ++) tmp_f[i - l] = f[i];
for (int i = 1; i < r - l + 1; i ++) tmp_g[i] = g[i];
NTT(tmp_f, L, +1), NTT(tmp_g, L, +1);
for (int i = 0; i < L; i ++) tmp_f[i] = 1ll * tmp_f[i] * tmp_g[i] % mod;
NTT(tmp_f, L, -1);
for (int i = mid + 1; i <= r; i ++) add(f[i], tmp_f[i - l]);
solve(mid + 1, r);
}
多项式求逆
\(\mathcal{O}(n^2)\) 递推求逆:运用逆多项式的定义展开 \(f^{-1}\),有
\(\mathcal{O}(n \log n)\) 倍增求逆:
首先有
若已经求出了 \(f(x)\) 在模 \(x^{\left\lceil\frac{n}{2}\right\rceil}\) 意义下的逆元 \(f_0^{-1}(x)\),则
将上式的两边平方,得
将上式的两边同乘 \(f(x)\) 移项后,得
倍增求逆即可。
const int SZ = N * 4;
int n;
int a[SZ];
int L, P;
int rev[SZ];
void poly_init(const int &n) {
L = 1, P = 0;
while (L <= n) L <<= 1, P ++;
for (int i = 1; i < L; i ++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (P - 1));
}
int omega_buc[SZ][2], inv_buc[SZ];
void NTT(int *a, const int &n, const int &opt) {
for (int i = 1; i < n; i ++)
if (i < rev[i]) std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1) {
int omega = omega_buc[k][opt == 1 ? 0 : 1];
for (int i = 0; i < n; i += (k << 1)) {
int w = 1;
for (int j = 0; j < k; j ++, w = 1ll * w * omega % mod) {
int u = a[i + j], v = 1ll * w * a[i + j + k] % mod;
add(a[i + j] = u, v), dec(a[i + j + k] = u, v);
}
}
}
if (opt == -1)
for (int i = 0; i < n; i ++) a[i] = 1ll * a[i] * inv_buc[n] % mod;
}
void poly_inv(int *a, const int &n) {
static int b[SZ], tmp_a[SZ], tmp_b[SZ];
b[0] = qpow(a[0], mod - 2, mod);
int m = 1;
while (m <= n) {
m <<= 1;
poly_init((m << 1) - 1);
for (int i = 0; i < m; i ++) tmp_a[i] = a[i];
for (int i = 0; i < m; i ++) tmp_b[i] = b[i];
NTT(tmp_a, L, +1), NTT(tmp_b, L, +1);
for (int i = 0; i < L; i ++)
b[i] = ((2ll * tmp_b[i] - 1ll * tmp_b[i] * tmp_b[i] % mod * tmp_a[i]) % mod + mod) % mod;
NTT(b, L, -1);
for (int i = m; i < L; i ++) b[i] = 0;
}
for (int i = 0; i <= n; i ++) a[i] = b[i];
}
FWT
FWT 补充界:对于一个多项式,需要对其最高位补 \(0\),将其补成一个最高项次数为 \(2^k - 1(k \in \N^*)\) 的多项式。
FWT 或(or)卷积:
-
正变换:\(a_i' = \sum_{j \subseteq i} a_j\),相当于状态 \(i\) 的值变换成状态 \(i\) 的子集和。将所有状态按照最高位的值分成 \(0/1\) 两组,分别递归处理两组的正变换后,将 \(0\) 组的贡献计入 \(1\) 组。
-
逆变换:\(a_i = \sum_{j \subseteq i}(-1)^{|i| - |j|} a'_j\)。将正变换中的贡献扣出,即可得到逆变换。
void FWT_or(int *a, const int &n, const int &opt) {
for (int k = 1; k < n; k <<= 1)
for (int i = 0; i < n; i += (k << 1))
for (int j = 0; j < k; j ++)
opt > 0 ? add(a[i + j + k], a[i + j]) : dec(a[i + j + k], a[i + j]);
}
FWT 与(and)卷积:
-
正变换:\(a_i' = \sum_{i \subseteq j} a_j\),相当于状态 \(i\) 的值变换成状态 \(i\) 的超集和。将所有状态按照最高位的值分成 \(0/1\) 两组,分别递归处理两组的正变换后,将 \(1\) 组的贡献计入 \(0\) 组。
-
逆变换:\(a_i = \sum_{i \subseteq j}(-1)^{|j| - |i|} a'_j\)。将正变换中的贡献扣出,即可得到逆变换。
void FWT_and(int *a, const int &n, const int &opt) {
for (int k = 1; k < n; k <<= 1)
for (int i = 0; i < n; i += (k << 1))
for (int j = 0; j < k; j ++)
opt > 0 ? add(a[i + j], a[i + j + k]) : dec(a[i + j], a[i + j + k]);
}
FWT 异或(xor)卷积:
- 正变换:\(a'_i = \sum_j (-1)^{|i \& j|} a_j\)。将所有状态按照最高位的值分成 \(0/1\) 两组,分别递归处理两组的正变换后,将 \(0, 1\) 组的贡献分别乘 \(+1, +1\) 计入 \(0\) 组,将 \(0, 1\) 组的贡献分别乘 \(+1, -1\) 计入 \(1\) 组。
- 逆变换:\(a_i = \frac{1}{n}\sum_j (-1)^{|i \& j|} a'_j\)。在正变换结束后除以 \(n\),即可得到逆变换。
void FWT_xor(int *a, const int &n, const int &opt) {
for (int k = 1; k < n; k <<= 1)
for (int i = 0; i < n; i += (k << 1))
for (int j = 0; j < k; j ++) {
int u = a[i + j], v = a[i + j + k];
add(a[i + j] = u, v), dec(a[i + j + k] = u, v);
}
if (opt == -1) {
int inv = qpow(n, mod - 2, mod);
for (int i = 0; i < n; i ++) a[i] = 1ll * a[i] * inv % mod;
}
}
子集 dp
\(\mathcal{O}(2^nn)\) 子集 dp:将一个二进制状态看成一个 \(m\) 维的坐标,相当于是要求每个维度的值都小于等于当前坐标对应维度的值的所有坐标的权值和,运用高维前缀和即可。
for (int i = 0; i < m; i ++)
for (int S = 0; S < (1 << m); S ++)
if (S >> i & 1) // f[S] <- f[S ^ (1 << i)]
\(\mathcal{O}(2^nn^2)\) 半在线子集 dp:按照 \(|S|\) 从大到小分层计算,每一层做一遍子集 dp,更新当前层的所有状态。
for (int S = 1; S < (1 << m); S ++) bitcount[S] = bitcount[S ^ (S & -S)] + 1;
for (int s = 1; s <= m; s ++) {
for (int S = 0; S < (1 << m); S ++) // g[S] = f[S]
for (int i = 0; i < m; i ++)
for (int S = 0; S < (1 << m); S ++)
if (S >> i & 1) // g[S] <- g[S ^ (1 << i)]
for (int S = 0; S < (1 << m); S ++)
if (bitcount[S] == s) // f[S] <- g[S]
}
子集卷积
\(\mathcal{O}(2^nn^2)\) 子集卷积:记占位多项式
则
即
对所有 \(0 \leq i \leq m\),对所有 \(A_i\) 和 \(B_i\) 进行 FWT,处理得到所有 \(C_i\) 后再 IFWT 即可。
\(\mathcal{O}(2^nn^2)\) 半在线子集卷积:按照 \(|S|\) 从大到小的顺序计算,每一层做一遍子集卷积,更新当前层的所有状态。最后再 IFWT 即可。
C - 数学知识 初等数论
质数
质数分布函数 \(\pi(n)\):当 \(n\) 充分大时,\(\pi(n) \sim \frac{n}{\ln n}\)。
试除法判素数:\(\mathcal{O}(\sqrt{n})\)。
bool is_prime(int n) {
for (int i = 2; 1ll * i * i <= n; i ++)
if (n % i == 0) return 0;
return 1;
}
埃氏筛:\(\mathcal{O}(n \log \log n)\)。
bool vis[N];
void sieve(const int &N) {
for (int i = 2; i <= N; i ++) {
if (vis[i]) continue;
for (int j = 2 * i; j <= N; j += i) vis[j] = 1;
}
}
线性筛:\(\mathcal{O}(n)\)。
int pClock, prime[N], fac[N];
void sieve(const int &N) {
for (int i = 2; i <= N; i ++) {
if (!fac[i]) fac[i] = i, prime[++ pClock] = i;
for (int j = 1; j <= pClock; j ++) {
if (prime[j] > fac[i] || prime[j] > N / i) break;
fac[i * prime[j]] = prime[j];
}
}
}
试除法质因数分解:\(\mathcal{O}(\sqrt{n})\)。
void resolve(int n) {
for (int i = 2; 1ll * i * i <= n; i ++) {
if (n % i == 0) {
int p = i, c = 0;
while (n % i == 0) n /= i, c ++;
// Find the prime factor p^c
}
}
if (n > 1)
// Find the prime factor n
}
阶乘 \(n!\) 质因数分解:筛选出 \(1 \sim n\) 中所有的质数,对于每个质数 \(p\),阶乘 \(n!\) 包含质因子 \(p\) 的个数为 \(1 \sim n\) 中每个数包含质因子 \(p\) 的个数之和,即
时间复杂度 \(\mathcal{O}(n)\)。
质因数的根号分治:一个数 \(n\) 大于 \(\sqrt{n}\) 的质因子至多有 \(1\) 个。
约数
整除
整除:\(n \bmod d = 0 \Rightarrow d \mid n\),记作 \(d\) 整除 \(n\)。
试除法求 \(n\) 的正约数集合:\(\mathcal{O}(\sqrt{n})\)。
void get_factor(int n) {
for (int i = 1; 1ll * i * i <= n; i ++)
if (n % i == 0) {
// Find the factor i
if (i * i != n) // Find the factor n / i
}
}
试除法推论:\(n\) 的正约数个数不超过 \(2\sqrt{n}\)。
倍数法求 \(1 \sim n\) 中所有数的正约数集合:\(\mathcal{O}(n \log n)\)。
std::vector<int> g[N];
void get_factor(const int &N) {
for (int i = 1; i <= N; i ++)
for (int j = i; j <= N; j += i) g[j].push_back(i);
}
倍数法推论:\(1 \sim n\) 中所有数的正约数个数总和约为 \(n \log n\)。
命题:
更相减损术:对 \(\forall a, b \in \N\),不妨设 \(a \geq b\),有
辗转相除法(欧几里得算法):
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
整除性质 1:对于 \(\forall a, b, c \in \mathbb{Z}\),有
整除性质 2:对于给定的正整数 \(n\),正整数 \(d(1 \leq d \leq n)\) 所对应的 \(\left\lfloor \frac{n}{d} \right\rfloor\) 的取值不超过 \(2\sqrt{n}\) 种。
整除分块:
- 观察到函数 \(f(x) = \frac{n}{x}\) 在区间 \((0, +\infty)\) 上单调递减。故对于函数 \(g(x) = \left\lfloor \frac{n}{x} \right\rfloor\) 的任意一个函数值,令 \(g(x)\) 取到该函数值的所有自变量 \(x\) 组成一段连续的区间。根据「整除性质 2」,这样的区间个数是 \(\mathcal{O}(\sqrt{n})\) 级别的。
- 对于一段 \(g(x)\) 均相等的区间,我们可以直接得出该区间里的所有 \(x\) 对答案的贡献。
- 考虑任意一个 \(i(1 \leq i \leq n)\),现在要找到一个最大的 \(j(i \leq j \leq n)\),使得 \(\left\lfloor \frac{n}{i} \right\rfloor = \left\lfloor \frac{n}{j} \right\rfloor\)。结论:
for (int x = 1, nx; x <= n; x = nx + 1) {
nx = n / (n / x);
// Calculate the contribution of [x, nx]
}
高维整除分块:
- 在二维的情况下,考虑任意一个 \(i\),现在要找到一个最大的 \(j\),使得 \(\left\lfloor \frac{n}{i} \right\rfloor = \left\lfloor \frac{n}{j} \right\rfloor\) 且 \(\left\lfloor \frac{m}{i} \right\rfloor = \left\lfloor \frac{m}{j} \right\rfloor\)。易证:
- 在三维以上的情况下,也是一样的处理方式。
积性函数 & Dirichlet 卷积
积性函数:若数论函数 \(f(n)\),对于任意互质正整数 \(x, y\),都有 \(f(xy) = f(x)f(y)\),则 \(f(n)\) 称为积性函数。
完全积性函数:若数论函数 \(f(n)\),对于任意正整数 \(x, y\),都有 \(f(xy) = f(x)f(y)\),则 \(f(n)\) 称为完全积性函数。
积性函数的简单性质:
- 对于任意积性函数 \(f(x)\),有 \(f(1) = 1\)。
- 对于任意积性函数 \(f(x), g(x)\),以下的 \(h(x)\) 亦为积性函数
- 对于任意积性函数 \(f(x)\),将 \(x\) 唯一分解,有 \(f(x) = \prod\limits_{i = 1}^m f(p_i^{c_i})\)。
常见积性函数:
- 单位函数(完全积性):
- 恒等函数(完全积性):
- 常数函数(完全积性):
- 约数函数(积性):
- 欧拉函数(积性):
- 莫比乌斯函数(积性):
Dirichlet 卷积:对于两个数论函数 \(f, g\),定义其 Dirichlet 卷积为
Dirichlet 卷积的简单性质:
- 交换律:\(f \ast g = g \ast f\)。
- 结合律:\((f \ast g) \ast h = f \ast (g \ast h)\)。
- 分配律:\(f \ast (g + h) = f \ast g + f \ast h\)。
- 单位元:\(f \ast \epsilon = f\),因此 \(\epsilon\) 也被叫做 Dirichlet 卷积单位元。
常见 Dirichlet 卷积式:
乘积约数函数结论:
乘积欧拉函数结论:
线性筛求积性函数
线性筛求积性函数:
- 一般地,对于积性函数 \(f(x)\),若 \(f(p^c)\) 的值便于分析,则该函数的前 \(n\) 项的函数值可以被线性筛筛出来。
- 记 \(\text{low}_i\) 表示:若 \(i\) 的最小质因子为 \(p_1\),其次数为 \(c_1\),则 \(\text{low}_i = p_1^{c_1}\)。在线性筛的过程中
- 若当前筛出了一个质数 \(p\),则将 \(f(p), f(p^2), f(p^3), \cdots, f(p^c)\) 都计算出来。
- 若当前要计算至少包含两个质因子的 \(i\) 对应的函数值 \(f(i)\),由积性函数定义得 \(f(i) = f\left(\frac{i}{\text{low}_i}\right) \cdot f(\text{low}_i)\)。
int pClock, prime[N], low[N];
int f[N];
void sieve(const int &N) {
f[1] = 1;
for (int i = 2; i <= N; i ++) {
if (!low[i]) {
prime[++ pClock] = i;
s64 x = i; int c = 1;
while (x <= N) {
low[x] = x;
// calculate f(x)
x *= i, c ++;
}
}
for (int j = 1; j <= pClock; j ++) {
if (prime[j] > N / i) break;
int num = i * prime[j];
low[num] = i % prime[j] ? prime[j] : low[i] * prime[j];
f[num] = f[num / low[num]] * f[low[num]];
if (i % prime[j] == 0) break;
}
}
}
杜教筛
杜教筛:给出一个积性函数 \(f(x)\),记 \(S(n) = \sum\limits_{i = 1}^n f(i)\),则
故
此时我们得到了一个 \(S(n)\) 关于 \(S(\left\lfloor \frac{n}{i} \right\rfloor)\) 的递推式。若 \(f \ast g\) 与 \(g\) 的前缀和都较好处理的话,就可以考虑使用数论分块来处理上式,因此找到一个合适的数论函数 \(g\) 是关键。
可以积分近似证明,先线性筛预处理出 \(f(x)\) 的前 \(n^\frac{2}{3}\) 项,可以取得杜教筛的理论最优复杂度 \(\mathcal{O}(n^\frac{2}{3})\)。
同余
同余:\(a \bmod m = b \bmod m \Rightarrow a \equiv b \pmod m\),记作 \(a, b\) 模 \(m\) 同余。
同余类 \(\overline{a}\):集合 \(\{a + km\ \mid k \in \Z\}(a \in [0, m - 1])\) 中的所有数模 \(m\) 同余,余数都是 \(a\),该集合记作一个模 \(m\) 的同余类。
剩余系:由同余类组成的集合。
- 完全剩余系:\(\{\overline{a} \mid a \in [0, m - 1], a \in \Z\}\)。
- 简化剩余系:\(\{ \overline{a} \mid a \in [0, m - 1], a \in \Z, \gcd(a, m) = 1 \}\)。
费马小定理:若 \(p\) 是质数,则对于与 \(p\) 互质的正整数 \(a\),有 \(a^{p - 1} \equiv 1 \pmod p\)。
欧拉定理:对于与 \(m\) 互质的正整数 \(a\),有 \(a^{\varphi(m)} \equiv 1 \pmod m\)。
扩展欧拉定理:
裴蜀定理:对于任意整数 \(a, b\),存在一对整数 \(x, y\),使得 \(ax + by = \gcd(a, b)\)。
扩展欧几里得算法:
int exgcd(int a, int b, int &x, int &y) {
if (!b) { x = 1, y = 0; return a; }
int d = exgcd(b, a % b, x, y);
int z = x; x = y, y = z - (a / b) * y;
return d;
}
二元一次方程 \(ax + by = c\):
- 有解性:\(ax + by = c\) 有解当且仅当 \(\gcd(a, b) \mid c\)。
- 通解:首先找出一对整数 \(x_0, y_0\),使得 \(ax_0 + by_0 = \gcd(a, b)\),则有通解
线性同余方程 \(ax \equiv b \pmod m\):原方程等价于 \(ax - b\) 为 \(m\) 的倍数,不妨设为 \(-y\) 倍,则原方程转化为 \(ax + my = b\)。
- 有解性:\(ax \equiv b \pmod m\) 有解当且仅当 \(\gcd(a, m) \mid b\)。
- 通解:首先找出一对整数 \(x_0, y_0\),使得 \(ax_0 + my_0 = \gcd(a, m)\),则有通解
模数两两互质的一元线性同余方程组(中国剩余定理):
- 记所有模数的积为 \(m = \prod_{i = 1}^n m_i\)。
- 记第 \(i\) 个方程:
- 记 \(M_i = \frac{m}{m_i}\)。
- 记 \(M_i^{-1}\) 为 \(M_i\) 在模 \(m_i\) 意义下的逆元。
- 特解:\(x_0 = \sum_{i = 1}^n a_iM_iM_i^{-1}\)。
- 通解:\(x = x_0 + km(k \in \Z)\)。
不保证模数两两互质的一元线性同余方程组(扩展中国剩余定理):先考虑仅有两个方程的情况(多个方程的情况可以依次合并)。不妨设这两个方程为
则
求二元一次方程 \(m_1p - m_2q = a_2 - a_1\) 的一组特解 \(p_0, q_0\),记 \(a' = p_0m_1 + a_1, m' = \mathrm{lcm}(m_1, m_2)\),则方程组合并为
固定底数且与模数互质的高次同余方程 \(a^x \equiv b \pmod p\)(BSGS):记 \(t = \left\lceil\sqrt{p}\right\rceil\),设 \(x = i \cdot t - j(j \in [0, t - 1])\),则方程变为
对任意 \(j \in [0, t - 1]\),将 \(b \cdot a^j \bmod p\) 插入哈希表。枚举 \(i\) 的所有取值,即 \(i \in [0, t]\),计算 \((a^t)^i \bmod p\) 并在哈希表中查找对应的 \(j\),更新答案即可。时间复杂度 \(\mathcal{O}(\sqrt{p})\)。
- 形如底数与模数互质的高次同余方程 \(r \cdot a^x \equiv b \pmod p\),BSGS 依旧适用。
int BSGS(int a, int b, int p) {
if (b == 1 || p == 1) return 0;
if (a == 0) return b == 0 ? 1 : -1;
std::unordered_map<int, int> h; h.clear();
int t = sqrt(p) + 1;
for (int j = 0, x = b; j < t; j ++, x = 1ll * x * a % p) h[x] = j;
a = qpow(a, t, p);
for (int i = 0, x = 1; i <= t; i ++, x = 1ll * x * a % p) {
int j = h.find(x) == h.end() ? -1 : h[x];
if (j >= 0 && i * t - j >= 0) return i * t - j;
}
return -1;
}
固定底数且与模数不互质的高次同余方程 \(a^x \equiv b \pmod p\)(扩展 BSGS):原方程等价于 \(a^x - b\) 为 \(p\) 的倍数,不妨设为 \(-k\) 倍,则原方程转化为 \(a^x + kp = b\)。若 \(\gcd(a, p) \nmid b\),则无解;否则
将每次 \(a^{x - 1}\) 的系数 \(\frac{a}{\gcd(a, p)}\) 提取出来后,变成一个子问题,直到 \(r = b'\) 或 \(\gcd(a, p') = 1\) 停止递降。此时得到一个形如底数与模数互质的高次同余方程 \(r \cdot a^x \equiv b' \pmod {p'}\),套用 BSGS 即可。
int exBSGS(int a, int b, int p) {
if (b == 1 || p == 1) return 0;
if (a == 0) return b == 0 ? 1 : -1;
int d, m = 0, r = 1;
while (d = gcd(a, p), d ^ 1) {
if (b % d) return -1;
m ++;
b /= d, p /= d, r = 1ll * r * (a / d) % p;
if (r == b) return m;
}
if (p == 1) return m;
std::unordered_map<int, int> h; h.clear();
int t = sqrt(p) + 1;
for (int j = 0, x = b; j < t; j ++, x = 1ll * x * a % p) h[x] = j;
a = qpow(a, t, p);
for (int i = 0, x = r; i <= t; i ++, x = 1ll * x * a % p) {
int j = h.find(x) == h.end() ? -1 : h[x];
if (j >= 0 && i * t - j >= 0) return m + i * t - j;
}
return -1;
}
阶:若满足 \(a^x \equiv 1 \pmod m\) 的最小正整数 \(x\) 存在,则称 \(x\) 为 \(a\) 在模 \(m\) 意义下的阶,记作 \(\mathrm{ord}_m(a)\)。特别地,若 \(a, m\) 不互质,则认为 \(a\) 在模 \(m\) 意义下的阶为 \(+\infty\) 或不存在。
阶的简单性质:
-
\(a, a^2, \cdots, a^{\mathrm{ord}_m(a)}\) 模 \(m\) 两两不同余。
-
若 \(\mathrm{ord}_m(a)\) 存在,则 \(\mathrm{ord}_m(a) \mid \varphi(m)\)。
-
若 \(\gcd(a, m) = \gcd(b, m) = 1\),则
- 若 \(\gcd(a, m) = 1\),则
原根:若 \(\gcd(a, m) = 1\) 且 \(\mathrm{ord}_m(a) = \varphi(m)\),则称 \(a\) 为模 \(m\) 的原根。
原根存在定理:\(m\) 有原根,当且仅当 \(m = 2, 4, p^c, 2p^c\),其中 \(p\) 为奇质数。
最小原根:若 \(m\) 有原根,其最小原根不超过 \(m^{0.25}\) 级别。
原根个数:若 \(m\) 有原根,则 \(m\) 的原根个数为 \(\varphi(\varphi(m))\)。
固定次数的高次同余方程 \(x^a \equiv b \pmod p\)(BSGS):记 \(x = g^c\),其中 \(g\) 是 \(p\) 的原根,问题转化为
- 特解:\(x_0 \equiv g^c \pmod p\) 可以运用 BSGS 得到。
- 通解:\(x \equiv g^{c + \frac{\varphi(p)}{\gcd(a, \varphi(p))} \cdot i(i \in \Z)} \pmod p\)。
C - 数学知识 离散数学
集合论基础
集合并:\(A \bigcup B = \{ x \mid x \in A \ \mathtt{or} \ x \in B \}\)。
集合交:\(A \bigcap B = \{ x \mid x \in A \ \mathtt{and} \ x \in B \}\)。
集合补:\(\complement_U A = \{x \mid x \notin A \ \mathtt{and} \ x \in U \}\)。
集合相对补:\(B \setminus A = \{ x \mid x \notin A \ \mathtt{and} \ x \in B \}\)。
集合对称差:\(A \triangle B = \{ x \mid x \in A \ \mathtt{xor} \ x \in B \}\),同时
集合笛卡尔积:\(A \times B = \{ (x, y) \mid x \in A, y \in B \}\)。
集合反演律(德 · 摩根定律):
群论基础
群:若非空集合 \(S\) 和 \(S\) 上的运算 \(\cdot\) 构成的代数结构 \((S, \cdot)\),满足以下性质:
- 封闭性:对于 \(\forall a, b \in S\),有 \(a \cdot b \in S\)。
- 结合律:对于 \(\forall a, b, c \in S\),有 \((a \cdot b) \cdot c = a \cdot (b \cdot c)\)。
- 单位元:\(\exist e \in S\),使得对于 \(\forall a \in S\),有 \(e \cdot a = a \cdot e = a\)。
- 逆元:对于 \(\forall a \in S\),\(\exist b \in S\),使得 \(a \cdot b = b \cdot a = e\),则称 \(b\) 为 \(a\) 的逆元,记作 \(a^{-1}\)。
交换群(阿贝尔群):运算 \(\cdot\) 满足交换律的群。
半群:仅满足封闭性、结合律的代数结构 \((S, \cdot)\)。
幺半群:仅满足封闭性、结合律、单位元的代数结构 \((S, \cdot)\)。
有限群:个数有限的群。有限群的元素个数称作有限群的阶。
环:由非空集合 \(S\) 和 \(S\) 上的两个运算 \(+, \cdot\) 构成的代数结构 \((S, +, \cdot)\),满足以下性质:
- \((S, +)\) 是交换群。单位元为 \(0\),逆元为 \(-a\)。
- \((S, \cdot)\) 是半群。
- 分配律:对于 \(\forall a, b, c \in S\),有 \(a \cdot (b + c) = a \cdot b + a \cdot c\) 与 \((a + b) \cdot c = a \cdot c + b \cdot c\)。
交换环:运算 \(\cdot\) 满足交换律的环。
幺环:运算 \(\cdot\) 的单位元为 \(1\) 的环。
除环:任何非 \(0\) 均存在运算 \(\cdot\) 逆元的的环。
域:交换除环。
群的简单性质:
- 群中的单位元唯一。
- 群中的逆元唯一。逆元 \(=\) 左逆元 \(=\) 右逆元。
- 群中存在消去律。即对于 \(\forall a, b, c \in S\),有 \(ac = bc \iff a = b\)。
- 群中取乘积的逆时,元素要翻转。即 \((ab)^{-1} = b^{-1}a^{-1}\)。
子群:对于群 \(H(T, \cdot), G(S, \cdot)\),若 \(T \subseteq S\),则称 \(H\) 是 \(G\) 的子群。记作 \(H \leq G\)。
子群检验法:
- 群 \(G\) 的非空子集 \(H\) 是子群,只需对于 \(\forall a, b \in T\),有 \(a \cdot b^{-1} \in T\)。
- 群 \(G\) 的非空子集 \(H\) 是子群,只需满足封闭性,逆元存在。
- 群 \(G\) 的非空有限子集 \(H\) 是子群,只需满足封闭性。
陪集:对于群 \(G\) 的一个子群 \(H\) 和一个元素 \(a\),\(H\) 的左陪集 \(_aH = \{ a \cdot h \mid h \in H\}\),右陪集 \(H_a = \{ h \cdot a \mid h \in H \}\)。
陪集的简单性质:
- 对于 \(\forall a \in G\),有 \(|H| = |H_a|\)。
- 对于 \(\forall a \in G\),有 \(a \in H_a\)。
- \(H = H_a \iff a \in H\)。
- \(H_a = H_b \iff a \cdot b^{-1} \in H\)。
- \(H\) 的任意两个陪集要么相等、要么无交。
拉格朗日定理:若 \(H \leq G\),则 \(|H|\) 整除 \(|G|\),更进一步,有
其中 \([G : H]\) 表示 \(G\) 中 \(H\) 不同的陪集数。
循环群:由生成元 \(a\) 生成的群 \(\langle a \rangle = \{a^{k}, k \in \N^*\}\)。
阶:
- 对于群 \(G\) 中的元素 \(a\),元素 \(a\) 的阶定义为 \(\mathrm{ord}(a) = \min\{x \mid a^x = e, x \in \N^* \}\)。
- 对于群 \(G\),群 \(G\) 的阶定义为群 \(G\) 的元素个数。特别地,规定无限群的阶为 \(0\)。
阶的简单性质:
- 有限群中,元素 \(a\) 的阶一定存在,且 \(a^{-1} = a^{\mathrm{ord}(a) - 1}\)。
- 有限群中,元素 \(a\) 的阶等于元素 \(a\) 生成的循环群的阶,即 \(\mathrm{ord}(a) = \mathrm{ord}(\langle a \rangle)\)。
- 有限群中,元素 \(a\) 的阶 \(\mathrm{ord}(a) \mid \mathrm{ord}(G)\)。
- 有限群中,元素 \(a\) 满足 \(a^{\mathrm{ord}(G)} = e\)。
- 有限群中,若 \(\mathrm{ord}(G)\) 为质数,则群 \(G\) 为循环群。
置换:一个有限集合到自身的双射(一一对应关系)称为置换。不可重集 \(S = \{ a_1, a_2, \cdots, a_n \}\) 上的置换可以表示为
表示将 \(a_i\) 映射为 \(a_{p_i}\),即 \(f(a_i) = a_{p_i}\)。其中 \(p_1, p_2, \cdots, p_n\) 为 \(1 \sim n\) 的一个排列。
置换的乘法:对于两个置换 \(f=\left(\begin{array}{l}{a_{p_{1}}, a_{p_{2}}, \dots, a_{p_{n}}} \\{a_{q_{1}}, a_{q_{2}}, \dots, a_{q_{n}}}\end{array}\right)\) 和 \(g=\left(\begin{array}{c}{a_{1}, a_{2}, \ldots, a_{n}} \\{a_{p_{1}}, a_{p_{2}}, \ldots, a_{p_{n}}}\end{array}\right)\),\(f\) 和 \(g\) 的乘积记作 \(f \circ g\),其映射为
即 \((f \circ g)(x) = f(g(x))\),先经过了 \(g\) 的映射再经过了 \(f\) 的映射(映射的复合)。
置换群:通常记 \(\{1, 2, \cdots, n\}\) 上的所有置换构成的群称为 \(n\) 元对称群,记作 \(S_n\)。即
集合 \(S\) 上的所有置换,关于置换的乘法,满足封闭性、结合律、单位元、逆元,故 \((S_n, \circ)\) 构成群。
该群的任意一个子群称为置换群。
循环置换(轮换):集合 \(X\) 上特殊的置换,记 \(f = (a_1, a_2, \cdots, a_m)\),定义
若两个轮换的 \(a\) 不含有相同的元素,则称这两个轮换不相交。
- 任何置换都可以分解为若干个不相交轮换的乘积。
群作用:群 \(G\) 在集合 \(X\) 上的(左)群作用,是一个二元函数 \(\alpha:G \times X \to X\),满足
对于 \(g \in G, x \in X\),通常记 \(\alpha(g, x) = g(x)\),则上式等价于
稳定子:对于任意 \(x \in X\),定义 \(G^x = \{g \mid g(x) = x, g \in G\}\)。
轨道:对于任意 \(x \in X\),定义 \(G(x) = \{g(x) \mid x \in G\}\)。
不动点集合:对于任意 \(g \in G\),定义 \(X^g = \{x \in X \mid g(x) = x\}\)。
轨道-稳定子定理:对于任意 \(x \in X\),有
等价:若 \(a, b \in X\),\(a\) 与 \(b\) 等价(记作 \(a \sim b\))当且仅当 \(\exist g \in G, g(a) = b\),即 \(G(a) = G(b)\)。
等价类:由等价的元素构成的集合,记 \([x] = \{y \mid x \sim y\}\)。
\(X\) 关于 \(G\) 的轨道数:\(X / G = \{[x] \mid x \in X\} = \{G(x) \mid x \in X\}\)。
Burnside 引理
Burnside 引理:\(X\) 关于置换群 \(G\) 的轨道数,等于 \(G\) 中每个置换下 \(X\) 不动点的个数的算数平均数。即
Pólya 定理
Pólya 定理:定义 \(A, B\) 是两个有限集合,\(X = B^A\) 表示所有从 \(A\) 到 \(B\) 的映射,\(G\) 是作用在 \(A\) 上的一个置换群。
其中 \(c(g)\) 表示置换 \(g\) 拆出的不相交轮换数量。
C - 数学知识 线性代数
线性空间基础
线性空间:给定域 \(F\),\(F\) 上的向量空间 \(V\) 是一个集合,在 \(V\) 上定义了两种运算:
- 向量加法(\(+\)):\(V \times V \to V\),把 \(V\) 中的两个元素 \(u, v\) 变换成 \(V\) 中的另一个元素,记作 \(u + v\)。
- 标量乘法(\(\cdot\)):\(F \times V \to V\),把 \(F\) 中的一个元素 \(a\) 和 \(V\) 中的一个元素 \(u\) 变换成 \(V\) 中的另一个元素。记作 \(a \cdot u\)。
\(V\) 中的元素称为向量,\(F\) 中的元素称为标量。满足:
-
应用向量加法的 \(V\) 是交换群:
- 向量加法的结合律。
- 向量加法的交换律。
- 向量加法的单位元。
- 向量加法的逆元素。
-
应用标量乘法:
- 标量乘法与标量的域乘法相容。
- 标量乘法的单位元。
- 标量乘法对向量加法的分配律。
- 标量乘法对域加法的分配律。
线性组合:对于向量组 \(\alpha_1, ..., \alpha_n\),令 \(\alpha = k_1 \alpha_1 + ... + k_n\alpha_n\),则称 \(\alpha\) 是该向量组的一个线性组合。
线性表出:对于向量组 \(\alpha_1, ..., \alpha_n\),若 \(\alpha\) 是该向量组的一个线性组合,则称 \(\alpha\) 被该向量组线性表出。
张成空间:对于向量组 \(B\),其所有线性组合形成的集合称为 \(B\) 的张成空间,记作 \(\mathrm{span}(B)\)。
线性有关:若一个向量组中,存在一向量能被所有其他向量线性表出,则称该向量组线性有关。
- 若 \(B\) 线性相关,则存在 \(B' \subsetneq B\),使得 \(\mathrm{span}(B') = \mathrm{span}(B)\)。
线性基:对于向量组 \(\alpha_1, ..., \alpha_n\),其张成空间的一组线性无关的基底称为该向量组的线性基。
线性基的简单性质:
- 线性基 \(B\) 的任何一个真子集都不是 \(\mathrm{span}(B)\) 的线性基。
- \(\mathrm{span}(B)\) 中所有向量都可以按唯一的方式表达为 \(B\) 的某个线性组合。
- 空间 \(V\) 所有的线性基大小都是相同的,恰好为 \(V\) 的维度数。
异或线性基
异或线性基:对于向量组 \(\alpha_1, ..., \alpha_n\),其张成空间的一组线性无关的基底称为该向量组的线性基。记数组 \(p_i\) 表示最高位为 \(i\) 的基向量。
线性基 \(\mathcal{O}(w)\) 插入:插入一个向量 \(x\),从高位 \(\to\) 低位考虑,设当前考虑到第 \(i\) 位
- 若 \(p_i\) 不存在,则成功插入。
- 若 \(p_i\) 存在,则令 \(x \gets x \ \mathrm{xor} \ p_i\)。
线性基 \(\mathcal{O}(w^2)\) 合并:将其中一个线性基的 \(\mathcal{O}(w)\) 个基向量,单次 \(\mathcal{O}(w)\) 地插入另一个线性基即可。
struct bas {
int p[W + 1];
void insert(int x) {
for (int i = W; i >= 0; i --)
if (x >> i & 1) {
if (!p[i]) { p[i] = x; break; }
x ^= p[i];
}
}
void merge(bas x) {
for (int i = W; i >= 0; i --)
if (x.p[i]) insert(x.p[i]);
}
};
线性基离线删除:尝试线段树分治。
线性基在线删除:
- 若要删除的数 \(x\) 在线性基外,则直接删除即可。
- 若要删除的数 \(x\) 在线性基内,则先前未插入成功的数可能会插入到线性基中。没有在线性基中的数,一定是因为线性基中的若干个数可以异或得到。记录线性基外的数 \(y\) 都是由线性基中的哪些数异或得到的,记作集合 \(S_y\)。
- 若线性基外的某个数 \(y\) 的 \(S_y\) 包含 \(x\):此时 \(x\) 可以用 \(y\) 与线性基中其他数的异或和表示,那么用 \(y\) 来代替 \(x\),线性基不会变化。故删除 \(x\) 与删除 \(y\) 是等价的,将 \(y\) 删除即可。
- 若线性基外的所有数 \(y\) 的 \(S_y\) 都不包含 \(x\):此时必须删除 \(x\),记录线性基内的数 \(z\) 在插入线性基前异或过哪些数,记作集合 \(P_z\)。然后在线性基中寻找最小且 \(P\) 集合包含 \(x\) 的元素 \(z\),将 \(z\) 异或上线性基中 \(P\) 集合包含 \(x\) 的元素,即可消除 \(x\) 的影响。
线性基的线性表出判断:判断一个向量 \(x\) 能否被表出,只需模拟插入过程,若不可以成功插入,则可以被表出。
线性基求子集异或最大值 / 线性基求子集与 \(x\) 异或最大值:从高位 \(\to\) 低位贪心,异或更优就异或。
struct bas {
int p[W + 1];
void insert(int x) {
for (int i = W; i >= 0; i --)
if (x >> i & 1) {
if (!p[i]) { p[i] = x; break; }
x ^= p[i];
}
}
int ask_max(int x) {
int ans = x;
for (int i = W; i >= 0; i --)
if ((ans ^ p[i]) > ans) ans ^= p[i];
return ans;
}
};
线性基求非空子集异或最小值:若原向量组线性无关,则答案为最高位最低的基向量,否则答案为 \(0\)。
struct bas {
int p[W + 1];
bool exist;
void insert(int x) {
for (int i = W; i >= 0; i --)
if (x >> i & 1) {
if (!p[i]) { p[i] = x; break; }
x ^= p[i];
}
if (!x) break;
}
int ask_min() {
if (exist) return 0;
for (int i = 0; i <= W; i ++)
if (p[i]) return p[i];
return -1;
}
};
线性基求非空子集异或第 \(k\) 小值:
- 对于一个大小为 \(w\) 的线性基,若原向量组线性无关,则有 \(2^w - 1\) 种不同的异或值;否则有 \(2^w\) 种(包括 \(0\))。
- 查询前,重塑线性基,使得线性基的每一位互相独立。具体地,从高位 \(\to\) 低位重塑,设当前考虑到第 \(i\) 位,从 \(i - 1\) 到 \(0\) 枚举 \(j\),若 \(p_i\) 的第 \(j\) 位为 \(1\),则令 \(p_i \gets p_i \ \mathrm{xor} \ p_j\)。
- 查询时:
- 当原向量组线性有关时,若 \(k = 1\),则答案为 \(0\);否则转化为原向量组线性无关时,求非空异或第 \(k - 1\) 小值。
- 当原向量组线性无关时,将 \(k\) 二进制分解,若 \(k\) 的第 \(i\) 位为 \(1\),将答案异或上线性基中第 \(i\) 小的向量。
struct bas {
int p[W + 1];
bool exist;
void insert(int x) {
for (int i = W; i >= 0; i --)
if (x >> i & 1) {
if (!p[i]) { p[i] = x; break; }
x ^= p[i];
}
if (!x) exist = 1;
}
std::vector<int> pos;
int max_k;
void rebuild() {
for (int i = W; i >= 0; i --)
if (p[i]) {
for (int j = i - 1; j >= 0; j --)
if ((p[i] >> j & 1) && p[j]) p[i] ^= p[j];
}
for (int i = 0; i <= W; i ++)
if (p[i]) pos.push_back(i);
max_k = (1 << pos.size()) - 1;
if (exist) max_k ++;
}
int ask_kth(int k) {
if (k > max_k) return -1;
if (exist) k --;
int ans = 0;
for (int i = 0; i <= W; i ++)
if (k >> i & 1) ans ^= p[pos[i]];
return ans;
}
};
线性基求 \(x\) 在子集异或中的排名:
- 去重:对于线性基中第 \(i\) 小的基向量,若 \(x\) 在该位上为 \(1\),则将答案加上 \(2^{i}\)。
- 不去重:在张成空间中,每一个子集异或值的次数均为 \(2^{|V| - |B|}\)。故不去重的排名,为去重后的排名乘上 \(2^{|V| - |B|}\)。
struct bas {
int p[W + 1];
int else_rate = 1;
void insert(int x) {
for (int i = W; i >= 0; i --)
if (x >> i & 1) {
if (!p[i]) { p[i] = x; break; }
x ^= p[i];
}
if (!x) else_rate = 2 * else_rate % mod;
}
int ask(int x, bool type) {
int rk = 1, cur_rate = 1;
for (int i = 0; i <= W; i ++)
if (p[i]) {
if (x >> i & 1) rk = (rk + cur_rate) % mod;
cur_rate = 2 * cur_rate % mod;
}
if (type) rk = 1ll * rk * else_rate % mod;
return rk;
}
};
前缀线性基:记数组 \(p_i, q_i\) 分别表示最高位为 \(i\) 的基向量与最高位为 \(i\) 的基向量的元素构成中最左端的位置编号。
- 插入一个向量 \(x\),位置为 \(y\),从高位 \(\to\) 低位考虑,设当前考虑到第 \(i\) 位
- 若 \(p_i\) 不存在,则成功插入。
- 若 \(p_i\) 存在,此时若 \(q_i < y\),则交换基向量 \(p_i\) 与 \(x\) 且交换位置 \(q_i\) 与 \(y\),然后令 \(x \gets x \ \mathrm{xor} \ p_i\)。
- 查询由区间 \([l, r]\) 内的向量构成的线性基时,只须考虑版本 \(r\) 的线性基中 \(q_i \geq l\) 的基向量即可。
struct bas {
int p[W + 1];
int q[W + 1];
void insert(int x, int y) {
for (int i = W; i >= 0; i --)
if (x >> i & 1) {
if (!p[i]) { p[i] = x, q[i] = y; break; }
if (q[i] < y) std::swap(p[i], x), std::swap(q[i], y);
x ^= p[i];
}
}
} B[N];
int ask(int l, int r) {
for (int i = W; i >= 0; i --)
if (B[r].q[i] >= l) {
// ...
}
}
限定偶数个元素的线性基:将待插入的向量组 \(\alpha_1, ..., \alpha_n\) 全都异或上其中任意一个向量,新向量组建立的线性基,等于原向量组限定偶数个元素建立的线性基。
trick:记差分数组 \(b_i = a_i \oplus a_{i -1}\),在异或背景下有 \(\mathrm{span}(a_l, a_{l + 1}, \cdots, a_{r}) = \mathrm{span}(a_l, b_{l + 1}, \cdots, b_r)\)。
矩阵基础
同型矩阵:两个行数列数均相同的矩阵,互为同型矩阵。
主对角线:对于方阵 \(A\),所有元素 \(A_{i, i}\) 构成主对角线。
对角矩阵:对于方阵 \(A\),若对于 \(\forall i \ne j\) 均有 \(A_{i, j} = 0\),则称方阵 \(A\) 为对角矩阵,记作 \(\mathrm{diag}\{ \lambda_1, \cdots, \lambda_n\}\)。
单位矩阵:对于对角矩阵 \(A\),若对于 \(\forall i\) 均有 \(A_{i, i} = 1\),则称方阵 \(A\) 为单位矩阵,记作 \(I\)。
- 若矩阵乘法可以进行,则任意矩阵乘单位矩阵仍然不变。
对称矩阵:对于方阵 \(A\),若对于 \(\forall i, j\) 均有 \(A_{i, j} = A_{j, i}\),则称方阵 \(A\) 为对称矩阵。
三角矩阵:对于方阵 \(A\),若主对角线左下方的元素均为 \(0\),则称方阵 \(A\) 为上三角矩阵;若主对角线右上方的元素均为 \(0\),则称方阵 \(A\) 为下三角矩阵。
三角矩阵的简单性质:
- 两个上(下)三角矩阵的乘积,仍是上(下)三角矩阵。
- 若主对角线元素均非 \(0\),则上(下)三角矩阵可逆,矩阵的逆也是上(下)三角矩阵。
矩阵的线性运算:矩阵的线性运算分为加法、减法与数乘,均为逐个同位元素进行。只有同型矩阵之间可以相加减。
矩阵的乘法:对于 \(n \times p\) 的矩阵 \(A\) 与 \(p \times m\) 的矩阵 \(B\),则矩阵 \(A\) 与 \(B\) 的乘积为 \(n \times m\) 的矩阵 \(C\),其中
- 矩阵乘法不满足交换律,满足结合律。
矩阵的转置:对于矩阵 \(A\),其转置 \(A^{T}\) 满足对于任意 \(\forall i, j\) 均有 \(A^T_{j, i} = A_{i, j}\)。相当于将矩阵的行列互换。
矩阵转置的简单性质:
- \((AB)^T = B^TA^T\)。
方阵的逆:对于方阵 \(A\),若存在一个矩阵 \(P\) 使得 \(AP = PA = I\),则称 \(P\) 为 \(A\) 的逆矩阵,记作 \(A^{-1}\)。
方矩逆的简单性质:
- 若方阵 \(A, B\) 均可逆,则 \((AB)^{-1} = B^{-1}A^{-1}\)。
- 若方阵 \(A\) 均可逆,则 \(\left(A^T\right)^{-1} = \left(A^{-1}\right)^T\)。
方阵的行列式:对于方阵 \(A\),其行列式为
记作 \(\det A\) 或 \(|A|\)。
方矩行列式的简单性质:
- 行列式与其转置行列式相等(在行列式中,行与列的地位是对等的),即 \(\det A = \det A^T\)。
- 若行列式的某一行(列)的元素都是两项之和,则该行列式等于分出的两个行列式之和,即 \(\det A = \det A_1 + \det A_2\)。
- 若方阵 \(A\) 为方阵 \(A_1, A_2\) 的乘积,则方阵 \(A\) 的行列式等于方阵 \(A_1, A_2\) 的行列式之积,即 \(\det A = \det A_1 \times \det A_2\)。
- 三角矩阵 \(A\) 的行列式等于主对角线元素之积,即 \(\det A = \prod A_{i, i}\)。
初等矩阵:
- 倍乘矩阵 \(D_{i}(k)\):一个对角矩阵,主对角线上的第 \(i\) 个元素为 \(k(k \neq 0)\),其余元素均为 \(1\)。
- 对换矩阵 \(P_{i, j}\):一个元素均为 \(0\) 或 \(1\) 的对称矩阵,在主对角线上,除了第 \(i\) 个元素与第 \(j\) 个元素为 \(0\) 以外,其余元素均为 \(1\),同时第 \(i\) 行第 \(j\) 列与第 \(j\) 行第 \(i\) 列上的元素为 \(1\)。
- 倍加矩阵 \(T_{i, j}(k)\):一个在单位矩阵 \(I\) 的基础上,令第 \(i\) 行第 \(j\) 列元素为 \(k\) 的矩阵。
初等行变换:
-
倍乘变换:将第 \(i\) 行乘以 \(k\),即 \(A \to D_i(k)A\)。
-
对换变换:将第 \(i\) 行与第 \(j\) 行对换,即 \(A \to P_{i, j}A\)。
-
倍加变换:将第 \(j\) 行的 \(k\) 倍加到第 \(i\) 行,即 \(A \to T_{i, j}(k)A\)。
初等列变换:
-
倍乘变换:将第 \(i\) 列乘以 \(k\),即 \(A \to AD_i(k)\)。
-
对换变换:将第 \(i\) 列与第 \(j\) 列对换,即 \(A \to AP_{i, j}\)。
-
倍加变换:将第 \(j\) 列的 \(k\) 倍加到第 \(i\) 列,即 \(A \to AT_{i, j}(k)\)。
高斯消元
线性方程组:设由 \(n\) 个未知数,\(m\) 个方程组成的方程组
其中系数 \(a_{i, j}\),常数 \(b_j\) 均为已知数,\(x_i\) 为未知数。当 \(b_1, \cdots, b_m\) 全为 \(0\) 时,称该方程组为其次线性方程组;当 \(b_1, \cdots, b_m\) 不全为 \(0\) 时,称该方程组为非齐次线性方程组。
线性方程组的矩阵表示:\(AX = B\),其中 \(A\) 为系数矩阵,\(X\) 为未知数矩阵,\(B\) 为常数矩阵
线性方程组的增广矩阵:将系数矩阵 \(A\) 和常数矩阵 \(B\) 放在一起构成的矩阵,称为线性方程组的增广矩阵
线性方程组的求解:
-
若用初等行变换将增广矩阵 \([A \ B]\) 变化为 \([C \ D]\),则 \(AX = B\) 与 \(CX = D\) 为同解方程组。
-
高斯消元用初等行变换将增广矩阵化成阶梯型矩阵,再逐步回代,求出方程组的解。具体地,枚举每一行:
- 利用初等行变换中的对换变换,将行首起第一个非 \(0\) 元素当作该行的主元。
- 利用初等行变换中的倍乘变换,将此行的主元置为 \(1\)。
- 利用初等行变换中的倍加变换,将其他行中此元素消去(即置为 \(0\))。
-
算法结束后,每行的常数项列上的数,即为对应行主元变量的解。若某行中系数全为 \(0\),但常数项不为 \(0\),则该方程组无解。若某行全为 \(0\),则该行主元元素可取任意值,这种主元称为自由元。
int n;
double a[N][N];
void gauss() {
double rate;
for (int i = 1; i <= n; i ++) {
int r = i;
for (int j = i + 1; j <= n; j ++)
if (fabs(a[j][i]) > fabs(a[r][i])) r = j;
if (i ^ r)
for (int j = i; j <= n + 1; j ++) std::swap(a[i][j], a[r][j]);
if (fabs(a[i][i]) < eps) // No solution.
rate = a[i][i];
for (int j = i; j <= n + 1; j ++) a[i][j] /= rate;
for (int j = 1; j <= n; j ++) {
if (i == j) continue;
rate = a[j][i];
for (int k = i; k <= n + 1; k ++) a[j][k] -= rate * a[i][k];
}
}
}
int n;
int a[N][N];
void gauss() {
int rate;
for (int i = 1; i <= n; i ++) {
int r = i;
while (r <= n && !a[r][i]) r ++;
if (r == n + 1) // No solution.
if (i ^ r)
for (int j = i; j <= n + 1; j ++) std::swap(a[i][j], a[r][j]);
rate = qpow(a[i][i], mod - 2, mod);
for (int j = i; j <= n + 1; j ++) a[i][j] = 1ll * a[i][j] * rate % mod;
for (int j = 1; j <= n; j ++) {
if (i == j) continue;
rate = a[j][i];
for (int k = i; k <= n + 1; k ++) dec(a[j][k], 1ll * rate * a[i][k] % mod);
}
}
}
方阵求逆:
- 注意到初等行变换是让矩阵 \(A\) 左乘一个初等矩阵,故在高斯消元将矩阵 \(A\) 消成单位矩阵 \(I\) 的过程中,左乘的所有初等矩阵的乘积即为矩阵 \(A\) 的逆矩阵 \(A^{-1}\)。
- 构造增广矩阵 \([A \ I]\),利用高斯消元将增广矩阵消成 \([I \ A^{-1}]\)。若左半部分不能消成 \(I\),则矩阵 \(A\) 不可逆。
行列式求值:
- 利用高斯消元将方阵 \(A\) 消成上三角矩阵,则行列式的值等于上三角矩阵的主对角线乘积 \(\times\) 消元过程中左乘的所有初等矩阵的行列式乘积。
- 在模意义下,若乘法逆元存在,则需要使用乘法逆元代替除法运算;若乘法逆元不存在,则需要使用辗转相除法。
int n;
double a[N][N];
double det() {
double ans = 1;
for (int i = 1; i <= n; i ++) {
int r = i;
for (int j = i + 1; j <= n; j ++)
if (fabs(a[j][i]) > fabs(a[r][i])) r = j;
if (i ^ r) {
for (int j = i; j <= n; j ++) std::swap(a[i][j], a[r][j]);
ans = -ans;
}
if (fabs(a[i][i]) < eps) return 0;
for (int j = i + 1; j <= n; j ++) {
double rate = a[j][i] / a[i][i];
for (int k = i; k <= n; k ++) a[j][k] -= rate * a[i][k];
}
ans *= a[i][i];
}
return ans;
}
int n;
int a[N][N];
int det() {
int ans = 1;
for (int i = 1; i <= n; i ++) {
int r = i;
while (r <= n && !a[r][i]) r ++;
if (r == n + 1) return 0;
if (i ^ r) {
for (int j = i; j <= n; j ++) std::swap(a[i][j], a[r][j]);
ans = mod - ans;
}
int inv = qpow(a[i][i], mod - 2, mod);
for (int j = i + 1; j <= n; j ++)
if (a[j][i]) {
int rate = 1ll * a[j][i] * inv % mod;
for (int k = i; k <= n; k ++) dec(a[j][k], 1ll * rate * a[i][k] % mod);
}
ans = 1ll * ans * a[i][i] % mod;
}
return ans;
}
int n;
int a[N][N];
int det() {
int ans = 1;
for (int i = 1; i <= n; i ++) {
int r = i;
while (r <= n && !a[r][i]) r ++;
if (r == n + 1) return 0;
if (i ^ r) {
for (int j = i; j <= n; j ++) std::swap(a[i][j], a[r][j]);
ans = mod - ans;
}
for (int j = i + 1; j <= n; j ++) {
while (a[i][i]) {
int rate = a[j][i] / a[i][i];
for (int k = i; k <= n; k ++) dec(a[j][k], 1ll * rate * a[i][k] % mod);
for (int k = i; k <= n; k ++) std::swap(a[i][k], a[j][k]);
ans = mod - ans;
}
for (int k = i; k <= n; k ++) std::swap(a[i][k], a[j][k]);
ans = mod - ans;
}
ans = 1ll * ans * a[i][i] % mod;
}
return ans;
}
矩阵树定理
代数余子式:一个矩阵 \(M\) 的代数余子式 \(M[i, j]\),表示去掉第 \(i\) 行第 \(j\) 列后得到的行列式。
邻接矩阵 \(A\):描述了由点对间信息构成的矩阵。
- 在无向无权图中,\(A_{i, j}\) 表示从 \(i\) 指向 \(j\) 的边数(包括重边)。
- 在无向有权图中,\(A_{i, j}\) 表示从 \(i\) 指向 \(j\) 的边权之和。
度数矩阵 \(D\):描述了由主元信息构成的矩阵。当 \(i \neq j\) 时,\(D_{i, j}\) 为 \(0\)。
- 在无向无权图中,\(D_{i, i}\) 为点 \(i\) 的度数。
- 在无向有权图中,\(D_{i, i}\) 为点 \(i\) 连接的边的边权之和。
- 在有向图中,度数矩阵 \(D\) 分为出度矩阵 \(D^{\mathrm{out}}\) 与入度矩阵 \(D^{\mathrm{in}}\)。
基尔霍夫矩阵:\(K = D - A\)。
矩阵树定理 1(无向图):无向图 \(G\) 的所有生成树个数为
矩阵树定理 2(有向图,内向树):有向图 \(G\) 以 \(k\) 为根的内向生成树个数为
矩阵树定理 3(有向图,外向树):有向图 \(G\) 以 \(k\) 为根的外向生成树个数为
BEST 定理(有向欧拉图):有向欧拉图 \(G\) 的不同欧拉回路个数为
- 对于有向欧拉图 \(G\) 的任意两个节点 \(k, k'\),都有 \(t^{\mathrm{root}}(G, k) = t^{\mathrm{root}}(G, k')\),且欧拉图 \(G\) 的所有节点的入度与出度相等。
矩阵树定理求边权和:将边权 \(w\) 记作多项式 \(1 + wx\),则答案即为一次项的系数。
LGV 引理
LGV 引理前置:
- 起点集 \(A\):\(A = \{a_1, a_2, \cdots, a_n\}\),描述了由起点构成的集合。
- 终点集 \(B\):\(B = \{b_1, b_2, \cdots, b_n\}\),描述了由终点构成的集合。
- 不相交路径 \(F\):\(F = (P_1, P_2, \cdots,P_n)\),满足 \(P_i\) 是一条 \(a_i \to b_{p_i}\) 的路径,且对于任意不同的 \(i,j\) 均有 \(P_i, P_j\) 不相交。
- 路径积 \(\omega(P_i)\):描述路径 \(P_i\) 上所有边权的积。
- 路径积的和 \(e(u, v)\):\(e(u, v) = \sum\limits_{P:u \to v} \omega(P)\),描述所有 \(u \to v\) 的路径积之和。
LGV 引理:记
则
不相交路径计数:在一张有向无环平面图中,求从每个 \(a_i \to b_i\) 且路径不交的情况下,所有方案的路径积之和。由于只有满足 \(P_i\) 是一条 \(a_i \to b_i\) 的路径的情况下,\(F\) 才可能是一组不相交路径。故右式为
求 \(\det M\) 即可。
C - 数学知识 概率论
概率基础
样本点 \(\omega\):随机现象中可能发生的基本结果,即不能再细分的结果。
样本空间 \(\Omega\):全体样本点构成的集合。
随机事件:若干个样本点构成的集合,是样本空间 \(\Omega\) 的某个子集。
随机事件的运算:事件并(可记作 \(A + B\)),事件交(可记作 \(A \times B\)),事件差,事件补。
随机事件域 \(\mathcal{F}\):样本空间 \(\Omega\) 的某些子集所组成的子集类,满足事件关于并、交、差、补封闭,即
- \(\varnothing \in \mathcal{F}\)。
- 若有一个事件 \(A \in \mathcal{F}\),则对立事件 \(\overline{A} \in \mathcal{F}\)。
- 若有一列事件 \(A_i \in \mathcal{F}(i = 1, 2, ...)\),则事件并 \(\bigcup A_i \in \mathcal{F}\)。
概率的古典定义:若一个随机现象,其样本空间有限,且每个样本点出现的可能性是一样的,则对于事件 \(A\),有
概率的简单性质:
- 对于事件 \(A\),有
- 必然事件概率为 \(1\),不可能事件概率为 \(0\)
- 事件 \(A\) 与其对立事件的概率和为 \(1\)
- 若事件 \(A, B\) 满足 \(A \subseteq B\),则 \(P(A) \leq P(B)\)。
- 若事件 \(A, B\) 不交,则
- 对于事件 \(A, B\),有
- 对于事件 \(A, B\),有
条件概率:在事件 \(B\) 发生的情况下事件 \(A\) 发生的概率,记作 \(P(A \mid B)\)。
条件概率的简单性质:
- 概率乘法公式:
- 全概率公式:若一组事件 \(A_1, A_2, \cdots, A_n\) 为 \(\Omega\) 的不交覆盖,则
独立事件:若事件 \(A, B\) 满足 \(P(AB) = P(A)P(B)\),则称 \(A, B\) 独立。
期望基础
期望的定义:对于随机变量 \(X\),有
期望的简单性质:
- 对于随机变量 \(X\),有
- 若随机变量 \(X, Y\) 独立,则
- 对于随机变量 \(X, Y\),有
条件期望:在随机变量 \(Y = y\) 的情况下随机变量 \(X\) 的期望,记作 \(E(X \mid Y = y)\)。
条件期望的简单性质:
- 全期望公式:
Trick
转化
前缀最大值的概率
- 对于一个随机排列 \(a\),\(a_i\) 作为前缀最大值的概率为 \(\frac{1}{i}\)。
- 对于一个随机排列 \(a\),前缀最大值的期望个数为 \(\mathcal{O}(\log n)\)。
期望树高
- 随机父节点树的期望树高为 \(\mathcal{O}(\log n)\)。
- 随机树的期望直径为 \(\mathcal{O}(\sqrt{n})\)。
生日悖论
- 在 \([1, n]\) 中随机选数,选出两个相同数的期望轮数近似 \(2\sqrt{n}\)。
C - 数学知识 博弈论
公平组合游戏基础
公平组合游戏:满足以下三个条件的游戏。
- 由两名玩家交替行动,双方均知道游戏的完整信息。
- 游戏中的任意时刻,可以执行的合法行动与轮到哪名玩家无关。
- 游戏中的任意状态不能多次抵达,不能行动的玩家判负。
有向图游戏:一个有向无环图,图中有唯一的起点,在起点上放有一枚棋子。由两名玩家交替地,把这枚棋子沿着有向边移动,双方均知道有向图的完整信息。任意时刻,可供移动的有向边与轮到哪名玩家无关。任意节点不能多次抵达,不能行动的玩家判负。
公平组合游戏的简单性质:
- 没有后继状态的状态是必败状态。
- 一个状态是必胜状态,当且仅当至少存在一个后继状态为必败状态。
- 一个状态是必败状态,当且仅当所有后继状态均为必胜状态。
NIM 博弈
NIM 博弈:给定 \(n\) 堆物品,第 \(i\) 堆物品有 \(a_i\) 个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可以取完,但不能不取。不能取物品的玩家判负。若两人都采取最优策略,求先手是必胜还是必败。
NIM 博弈定理:NIM 博弈先手必胜,当且仅当 \(\mathrm{xor}_{i = 1}^n a_i \neq 0\)。
NIM 博弈定理证明:记 \(x = \mathrm{xor}_{i = 1}^n a_i\)。
- 对于必败状态 \(a_1 = a_2 = \cdots = a_n = 0\),有 \(x = 0\)。
- 对于一般状态:
- 当 \(x \neq 0\) 时,设 \(x\) 二进制下的最高位为 \(k\),则至少存在一个 \(a_i\) 的第 \(k\) 位为 \(1\),易证 \(a_i \ \mathrm{xor} \ x < a_i\),令 \(a_i \gets a_i \ \mathrm{xor} \ x\),有 \(x' = 0\)。
- 当 \(x = 0\) 时,无论如何取物品,均有 \(x' \neq 0\)。
- 对上述过程应用归纳法,可知 \(\mathrm{xor}_{i = 1}^n a_i \neq 0\) 为必胜态;\(\mathrm{xor}_{i = 1}^n a_i = 0\) 为必败态。
NIM-k 博弈
NIM-k 博弈:同 NIM 博弈,但每次可以任选 \(1, ..., k\) 堆。
NIM-k 博弈定理:NIM-k 博弈先手必胜,当且仅当将所有 \(a_i\) 二进制分解,在 \(k + 1\) 进制下做不进位加法,得出的结果不为 \(0\)。
阶梯博弈
阶梯博弈:给定 \(n\) 级阶梯,第 \(i\) 级阶梯上有 \(a_i\) 个物品。两名玩家轮流行动,每次可以任选一级阶梯,将任意多个物品移至下一级,可以移完,但不能不移。不能移物品的玩家判负。若两人都采取最优策略,求先手是必胜还是必败。
阶梯博弈定理:阶梯博弈等价于奇数层的 NIM 博弈。
SG 函数
mex 运算:设 \(S\) 表示一个非负整数集合。定义 \(\mathrm{mex}(S)\) 为不属于集合 \(S\) 的最小非负整数,即
SG 函数:在有向图游戏中,对于每个节点 \(x\),定义 \(\mathrm{SG}(x)\) 为 \(x\) 的后继节点 \(y_1, y_2, \cdots, y_k\) 的 SG 函数值后成的集合再执行 \(\mathrm{mex}\) 运算后得到的结果,即
有向图游戏的和:设 \(G_1, G_2, \cdots, G_m\) 是 \(m\) 个有向图游戏。定义有向图游戏 \(G\),它的行动规则是任选某个有向图游戏 \(G_i\),并在 \(G_i\) 上行动一步。\(G\) 被称为有向图游戏 \(G_1, G_2, \cdots, G_m\) 的和。可以证明
SG 函数上界:\(\mathcal{O}(\sqrt{m})\),其中 \(m\) 为有向图游戏中的有向边数。
SG 定理:一个有向图游戏 \(G\),当且仅当 \(\mathrm{SG}(G) \neq 0\) 为必胜局面,否则为必败局面。
SG 定理证明:
-
在单个有向图游戏 \(G\) 中。若一节点 \(x\) 满足 \(\mathrm{SG}(x) = 0\),则所有儿子 \(y\) 均满足 \(\mathrm{SG}(y) \neq 0\);若一个节点 \(x\) 满足 \(\mathrm{SG}(x) \neq 0\),则至少存在一个儿子 \(y\) 满足 \(\mathrm{SG}(y) = 0\)。可以归纳证明 \(\mathrm{SG}(G) \neq 0\) 为必胜态,否则为必败态。
-
在多个有向图游戏 \(G_1, G_2, \cdots, G_m\) 中。对于任意有向图 \(G_i\) 的起点 \(s_i\),可以移动到任意一个 \(\mathrm{SG}\) 值比它小或大的节点。
- 若移动到一个 \(\mathrm{SG}\) 值比它大的节点,则对方可以反手移动到一个 \(\mathrm{SG}\) 值与它相等的节点。故移动到一个 \(\mathrm{SG}\) 值比它大的节点是没有意义的。
- 若移动到一个 \(\mathrm{SG}\) 值比它小的节点,根据 \(\mathrm{SG}\) 函数的定义,对于任意 $0 \leq x < \mathrm{SG}(s_i) $,至少存在一个 \(s_i\) 的儿子 \(t\) 满足 \(\mathrm{SG}(t) = x\),故 \(\mathrm{SG}'(s_i)\) 可以变成任意一个小于 \(\mathrm{SG}(s_i)\) 的值。符合 NIM 博弈的模型。
Multi-SG:在有向图游戏中,某个状态的后继状态可以是多个有向图游戏的和。
Anti-SG
Anti 规则:同公平组合游戏,不能行动的玩家判胜。
SJ 定理:设 \(G\) 是有向图游戏 \(G_1, G_2, \cdots, G_m\) 的和,以 Anti 规则进行,则先手必胜当且仅当满足以下条件之一
-
\(\mathrm{xor}_{i = 1}^n \mathrm{SG}(G_i) \neq 0\) 且 \(\max_{i = 1}^n \mathrm{SG}(G_i) > 1\)。
-
\(\mathrm{xor}_{i = 1}^n \mathrm{SG}(G_i) = 0\) 且 \(\max_{i = 1}^n \mathrm{SG}(G_i) \leq 1\)。