数学与数论
白皮书第 6 章,进阶指南 0x03。
0xFF 一些链接
\(\color{red}\texttt{【数学2-1】进阶数论}\) | \(\color{red}\texttt{《算法竞赛进阶指南》第四章—数学知识}\) | \(\color{red}\texttt{OI-wiki-数学}\)
0x00 取模
模运算,常用于大数计算,当答案数值非常大时,通常会有两种情况,第一种是取模,通过模上一个较小的数来方便输出和计算;还有一种是高精度,有这个的题目就会比较恶心。
取模有以下性质:
- 可加性:\(\texttt{(a + b) mod m = ((a mod m) + (b mod m)) mod m}\)
- 可减性:\(\texttt{(a - b) mod m = ((a mod m) - (b mod m)) mod m}\),c++实现时为了避免出现负数,通常要在括号中加上模数 \(m\)
- 可乘性:\(\texttt{(a}\, \times\, \texttt{b) mod m = ((a mod m)}\, \times \, \texttt{(b mod m)) mod m}\)
- 没有可除性!除法取模等于乘除数的逆元。
0x01 乘法取模
两数直接乘,可能导致爆 long long
导致寄掉(噢当然你可以用 __int128
,数据实在太大则考虑是否需要使用高精度),这里给出一个不一定在所有情况下都有用的解决办法。
原理:\(x \times y\) 会爆,可以考虑变成 \((x \times 2) \times (y\ /\ 2)\),然后变成 \((x \times 2^2) \times (y\ /\ 2^2)\),以此类推,直到 \(y = 0\),详细证明见白皮书第 \(389\) 页。
如果 \(x\) 还是不幸爆炸了,那么就需要考虑高精度,但这个做法基本上可以规避掉大部分情况。
using ll = long long;
ll mul (ll x, ll y, ll mod) { // (x * y) % mod
x %= mod, y %= mod; // 首先规避一下
ll res = 0;
while (y) {
if (y & 1) { // 判断奇偶性
res = (res + x) % mod;
}
x = x * 2 % mod, y >>= 1;
}
return res;
}
0x10 快速幂
计算 \(x^n \bmod m\),朴素算法当然是一个个数去乘,边乘边取模,时间复杂度 \(O(n)\),十分不优秀。
一种做法是分治法,看白皮书去。
另一种就是位运算,时间复杂度 \(O(\log n)\),十分优秀,是标准的做法。
举个例子,求 \(a^{11}\ =\ ?\),朴素算法当然能做,但肯定不会去讲。
将 \(a^{11}\) 分解成 \(a^8 \times a^2 \times a\),每个数的幂次都是 \(2\) 的倍数。 那么就可以方便地计算了。
求幂次,用二进制分解,\(11 = (1011)_2\),跳过 \(a^4\) 即可。
具体实现看代码。
using ll = long long;
ll qmod (ll x, ll y, ll mod) { // x ^ y % mod
ll sum = 1;
while (y) {
if (y % 2) {
sum = (sum * x) % mod;
}
y >>= 2, x = (x * x) % mod;
}
return sum;
}
0x20 gcd 和 lcm
最大公约数和最小公倍数研究整除等问题,也可以用在概率问题的分数通分等等。
备注:
- 最大公约数,\(\texttt{Greatest Common Divisor(GCD)}\),\(\texttt{Greatest Common Denominator(GCD)}\),\(\texttt{Greatest Common Factor(GCF)}\),\(\texttt{Highest Common Factor(HCF)}\)
- 最小公倍数,\(\texttt{Least Common Multiple(LCM)}\)
0x21 GCD
整数 \(a\) 与 \(b\) 的最大公约数,就是一个最大的整数 \(c\) 且 \(c \mid a\)、\(c \mid b\),记作 \(\gcd(a,b)\)。
注意,因为是最大公约数,而 \(\left\vert a\right\vert\) 又是 \(a\) 的约数(\(a\) 无论是负数或非负数),所以对于任意的 \((a,b)\),有 \(\gcd(a,b) = \gcd(\left\vert a\right\vert, \left\vert b\right\vert)\)。
GCD有以下性质:
- \(\gcd(a,b) = gcd(a, k \times a + b)\)
- \(\gcd(k \times a, k \times b) = k \times \gcd(a,b)\)
- 多个整数的 GCD:\(\gcd(a,b,c,d) = \gcd(\gcd(a, b), \gcd(c, d))\),即 GCD 具有交换律和结合律(可以这么叫吧,理解万岁)
- 若 \(\gcd(a,b) = d\),则 \(\gcd(\frac{a}{d}, \frac{b}{d}) = 1\),即 \(\frac{a}{d}\) 与 \(\frac{b}{d}\) 互质
- \(\gcd(a + c \times b, b) = \gcd(a,b)\)
代码有几种,当然你也可以使用自带的函数 __gcd(a, b)
,但要确保 \(a \geqslant 0\) 且 \(b \geqslant 0\)。
\(1^{\texttt{st}}\)欧几里得算法
辗转相除法,是最常用的写法,唯一的缺点就是取模常数太大了,复杂度参考白皮书。
int gcd (int x, int y) {
return (y ? gcd(y, x % y) : x);
}
极简版
int gcd (int a, int b) {
while (a ^= b ^= a ^= b %= a) {
}
return b;
}
\(2^{\texttt{nd}}\) 更相减损术
基于性质:\(\gcd(a,b)=\gcd(b,a-b)=\gcd(a,a-b)\)。
计算步骤:\(\gcd(a,b)=\begin{cases} a & {a = b}\\\gcd(a-b,b) & {a > b}\\\gcd(a,b - a) & {a < b} \end{cases}\)
int gcd (int x, int y) {
while (x != y) {
if (x > y) {
x -= y;
} else {
y -= x;
}
}
return x;
}
更相减损术虽然避免了取模的常数,但在最极端的情况下时间复杂度可以达到 \(O(\max(a,b))\),实在不够优秀。在求 \(\gcd(10^9,1)\) 时就会爆炸。
\(3^{\texttt{rd}}\) Stein 算法
基于更相减损术,对于一些情况进行讨论和优化,具体如下:
- \(a\) 和 \(b\) 同为偶数,\(\gcd(a,b) = 2 \times \gcd(\frac{a}{2}, \frac{b}{2})\)
- \(a\) 和 \(b\) 同为奇数,\(\gcd(a,b) = \gcd(\frac{a+b}{2}, \frac{a-b}{2})\)
- \(a\) 奇 \(b\) 偶,根据性质 \(2\) 可得 \(\gcd(a,b)=\gcd(\frac{a}{2}, b)\)
- \(a\) 偶 \(b\) 奇,根据性质 \(2\) 可得 \(\gcd(a,b)=\gcd(a,\frac{b}{2})\)
结束条件仍然是 \(a = b\) 时返回 \(a\)。
using ll = long long;
ll gcd (ll a, ll b) {
if (a < b) {
return gcd(b, a);
} else if (a == b) {
return a;
}
if (a % 2 == b % 2) {
return (a % 2 == 0 ? 2 * gcd(a / 2, b / 2) : gcd((a + b) / 2, (a - b) / 2));
}
return (a % 2 == 0 ? gcd(a / 2, b) : gcd(a, b / 2));
}
0x22 LCM
\(\texttt{有 LCM,必有 GCD!!!1}\ \ \ \ \ \ \texttt{——鲁迅}\)
推论自己看白皮书,结论就是 \(\texttt{lcm(a,b) = a}\,\times\,\texttt{b / gcd(a,b)}\)
为了防止爆炸,可以先除再乘。
int lcm (int x, int y) {
return 1ll * x / gcd(x, y) * y;
}
0x23 裴蜀定理
又名贝祖定理(Bézout's lemma),是关于 GCD 的一个定理。
- 初步定理:若有一个整数二元组 \(\texttt{(x, y)}\),则必然存在一个整数二元组 \(\texttt{(a, b)}\) 使得 \(a \times x + b \times y = \gcd(x,y)\),这个等式被称为 Bézout 等式。
- 推论:整数 \(a\) 与 \(b\) 互质当且仅当存在整数 \(x\) 和 \(y\) 使得 \(a \times x + b \times y = 1\)。
0x30 扩展欧几里得算法
简称扩欧(Extended Euclidean algorithm, EXGCD),常用于求 \(ax + by = c\) 的一组特解,过程自己看OI-wiki或白皮书,代码:
int exgcd (int a, int b, ll &x, ll &y) {
if (!b) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
swap(x, y), y -= x * (a / b);
return d;
}
0x40 同余
同余是数论的一个基本理论,是很巧妙的工具,它使人们能够用等式的形式简洁地描述整除关系。相关内容有欧拉定理、费马小定理、扩欧、乘法逆元、线性同余方程、中国剩余定理等等。
0x41 同余的定义
- 同余的定义:设 \(m\) 为正整数,\(a\) 和 \(b\) 都为整数,若 \(m \mid (a-b)\),则称 \(a\) 和 \(b\) 模 \(m\) 同余。
- 记作 \(a \equiv b \pmod{m}\)。
- 同样也不用考虑 \(a\) 和 \(b\) 的正负,其实就是要带个绝对值。
- 剩余系:一个模 \(m\) 完全剩余系是一个整数的集合,使得每个整数都与此集合中的一个整数模 \(m\) 同余,例如 \(\left[0,1,2 \ldots m-1 \right]\) 就是模 \(m\) 完全剩余系,称为模 \(m\) 最小非负剩余的集合,剩余系在线性同余方程中有应用。
0x42 同余的定理及性质
首先,\(a,b,c,d,m\) 均为整数。
\(a \equiv b \pmod{m}\) 当且仅当 \(a \bmod m = b \bmod m\)。
把同余式转成等式,即 \(a = b + km\),\(k\) 为整数,这说明了同余方程与线性丢番图方程的关系。
设 \(m\) 为正整数,则模 \(m\) 有以下性质:
- 自反性:\(a \equiv a \pmod{m}\)
- 对称性:若 \(a \equiv b \pmod{m}\),则 \(b \equiv a \pmod{m}\)
- 传递性:若 \(a \equiv b \pmod{m}\)、\(b \equiv c \pmod{m}\),则 \(a \equiv c \pmod{m}\)
同余具有可加可减可乘可乘方性,若 \(a \equiv b \pmod{m}\)、\(c \equiv d \pmod{m}\),则:
- 可加性:\(a + c \equiv b + c \pmod{m}\),甚至 \(a + c \equiv b + d \pmod{m}\)。
- 可减性:\(a - c \equiv b - c \pmod{m}\),甚至 \(a - c \equiv b - d \pmod{m}\)。
- 可乘性:\(ac \equiv bc \pmod{m}\),甚至 \(ac \equiv bd \pmod{m}\)。
- 同样不存在可除性,除法取模需要逆元。
- 同余的幂(可乘方性):对于任意正整数 \(k\),\(a^k \equiv b^k \pmod{m}\)
0x43 一元线性同余方程
一元线性同余方程,即给定 \(a,b,m\),求整数 \(x\) 满足:\(ax \equiv b \pmod{m}\)
研究这个有啥用?\(ax \equiv b \pmod{m}\) 表示 \(ax - b\) 是 \(m\) 的倍数,设为 \(-y\) 倍,则有 \(ax + my = b\),这就是二元线性丢番图方程。。。(胡言乱语)
0x44 乘法逆元
模板题:P3811 【模板】模意义下的乘法逆元。
乘法逆元主要有三种求法:扩欧、费马小定理、递推预处理。
扩欧
-
优点:可以求出 \(m\) 不为质数时的同余方程解。
-
缺点:和费马小定理差不多。
\(ax\equiv 1\pmod m\),转为 \(ax+bm = 1\),先求出特解 \(x_0\),则通解为 \(x=x_0+mn\),最小整数解就是 \(((x_0 \mod m) + m) \mod m\)。
#include <iostream>
using namespace std;
using ll = long long;
ll x, y, z, w;
void exgcd (ll x, ll y, ll &z, ll &w) {
if (!y) {
z = 1, w = 0;
return ;
}
exgcd(y, x % y, w, z), w -= x / y * z;
}
int main () {
ios::sync_with_stdio(0), cin.tie(0);
cin >> x >> y;
exgcd(x, y, z, w);
cout << (z % y + y) % y;
return 0;
}
费马小定理
-
优点:复杂度为 \(O(\log m)\),处理较少元素逆元十分迅速。
-
缺点:当元素个数来到更高数量级时就不行了,或者 \(m\) 不为质数。
费马小定理,大致就是说 \(x^{m-1} \equiv 1 \pmod{m}\),那么可以推出 \(\frac{1}{x}\equiv x^{m-2} \pmod{m}\)。
用快速幂处理即可。
递推预处理
-
优点:当需要处理元素在一个 \(O(n)\) 能够处理的范围内时,可以快速求出它们的逆元。
-
缺点:当元素很大时无能为力,或者 \(m\) 不为质数。
证明自己翻书,代码如下:
const int N = 1e5 + 10;
int inv[N];
inv[1] = 1;
for (int i = 2; i <= n; i++) {
inv[i] = 1ll * (m - m / i) * inv[m % i] % m;
}
0x60 欧拉函数
欧拉函数(Euler's totient function),即 \(\varphi(n)=\sum\limits_{1\leqslant i\leqslant n}(\gcd(i,n)=1)\),当 \(n\) 为质数时明显有 \(\varphi(n)=n-1\)。
0x61 性质
- 欧拉函数是积性函数,即当 \(\gcd(a,b)=1\) 时,\(\varphi(a\times b)=\varphi(a)\times\varphi(b)\)。
- \(n=\sum\limits_{1\leqslant i\leqslant n \& i \mid n} \varphi(i)\),详细证明见 OI-wiki。
- 详细见 OI-wiki。
0x62 欧拉定理
有个正整数 \(m\),和一个整数 \(a\),若 \(\gcd(a,m)=1\),则有 \(a^{\varphi(m)} \equiv 1 \pmod m\)。
0x63 求 \(1\sim n\) 的欧拉函数值
可以在进行欧拉筛的同时计算出每个数的欧拉函数值。
// pri 记录的是质数,phi 记录的是欧拉函数值
f[1] = 1;
for (int i = 2; i <= n; i++) {
if (!f[i]) {
pri[++cnt] = i, phi[i] = i - 1;
}
for (int j = 1; j <= cnt && i * pri[j] <= n; j++) {
f[pri[j] * i] = 1, phi[pri[j] * i] = phi[i] * (pri[j] - bool(i % pri[j]));
if (i % pri[j] == 0) {
break;
}
}
}
0xE0 随手记
0xE1
给出四条边,如何判断是否能够组成梯形。
换句话说,就是梯形的四条边之间有什么关系。
结论:两腰和 > 两底差,两腰差 < 两底差
0xE2 海伦公式
简单来说,就是给出三角形三边长度 \(a\)、\(b\) 和 \(c\),求三角形面积的一个公式。
令 \(p = \frac{a+b+c}{2}\),那么三角形面积就是 \(\sqrt{p\times(p-a)\times(p-b)\times(p-c)}\)
0xE3 \(C_a^b\)
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, mod = 1e9 + 7;
int n, inv[N], x[N], f[N], a, b;
int C (int a, int b) {
return 1ll * f[a] * x[b] % mod * x[a - b] % mod;
}
int main () {
ios::sync_with_stdio(0), cin.tie(0);
inv[1] = x[0] = f[0] = 1;
for (int i = 2; i <= 2e5; i++) {
inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
}
for (int i = 1; i <= 2e5; i++) {
x[i] = 1ll * x[i - 1] * inv[i] % mod, f[i] = 1ll * f[i - 1] * i % mod;
}
return 0;
}