数学与数论
白皮书第 6 章,进阶指南 0x03。
0xFF 一些链接
0x00 取模
模运算,常用于大数计算,当答案数值非常大时,通常会有两种情况,第一种是取模,通过模上一个较小的数来方便输出和计算;还有一种是高精度,有这个的题目就会比较恶心。
取模有以下性质:
- 可加性:
- 可减性:
,c++实现时为了避免出现负数,通常要在括号中加上模数 - 可乘性:
- 没有可除性!除法取模等于乘除数的逆元。
0x01 乘法取模
两数直接乘,可能导致爆 long long
导致寄掉(噢当然你可以用 __int128
,数据实在太大则考虑是否需要使用高精度),这里给出一个不一定在所有情况下都有用的解决办法。
原理:
如果
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 快速幂
计算
一种做法是分治法,看白皮书去。
另一种就是位运算,时间复杂度
举个例子,求
将
求幂次,用二进制分解,
具体实现看代码。
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
最大公约数和最小公倍数研究整除等问题,也可以用在概率问题的分数通分等等。
备注:
- 最大公约数,
, , , - 最小公倍数,
0x21 GCD
整数
注意,因为是最大公约数,而
GCD有以下性质:
- 多个整数的 GCD:
,即 GCD 具有交换律和结合律(可以这么叫吧,理解万岁) - 若
,则 ,即 与 互质
代码有几种,当然你也可以使用自带的函数 __gcd(a, b)
,但要确保
欧几里得算法
辗转相除法,是最常用的写法,唯一的缺点就是取模常数太大了,复杂度参考白皮书。
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; }
更相减损术
基于性质:
计算步骤:
int gcd (int x, int y) { while (x != y) { if (x > y) { x -= y; } else { y -= x; } } return x; }
更相减损术虽然避免了取模的常数,但在最极端的情况下时间复杂度可以达到
Stein 算法
基于更相减损术,对于一些情况进行讨论和优化,具体如下:
和 同为偶数, 和 同为奇数, 奇 偶,根据性质 可得 偶 奇,根据性质 可得
结束条件仍然是
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
推论自己看白皮书,结论就是
为了防止爆炸,可以先除再乘。
int lcm (int x, int y) { return 1ll * x / gcd(x, y) * y; }
0x23 裴蜀定理
又名贝祖定理(Bézout's lemma),是关于 GCD 的一个定理。
- 初步定理:若有一个整数二元组
,则必然存在一个整数二元组 使得 ,这个等式被称为 Bézout 等式。 - 推论:整数
与 互质当且仅当存在整数 和 使得 。
0x30 扩展欧几里得算法
简称扩欧(Extended Euclidean algorithm, EXGCD),常用于求
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 同余的定义
- 同余的定义:设
为正整数, 和 都为整数,若 ,则称 和 模 同余。- 记作
。 - 同样也不用考虑
和 的正负,其实就是要带个绝对值。
- 记作
- 剩余系:一个模
完全剩余系是一个整数的集合,使得每个整数都与此集合中的一个整数模 同余,例如 就是模 完全剩余系,称为模 最小非负剩余的集合,剩余系在线性同余方程中有应用。
0x42 同余的定理及性质
首先,
把同余式转成等式,即
设
- 自反性:
- 对称性:若
,则 - 传递性:若
、 ,则
同余具有可加可减可乘可乘方性,若
- 可加性:
,甚至 。 - 可减性:
,甚至 。 - 可乘性:
,甚至 。 - 同样不存在可除性,除法取模需要逆元。
- 同余的幂(可乘方性):对于任意正整数
,
0x43 一元线性同余方程
一元线性同余方程,即给定
研究这个有啥用?
0x44 乘法逆元
模板题:P3811 【模板】模意义下的乘法逆元。
乘法逆元主要有三种求法:扩欧、费马小定理、递推预处理。
扩欧
-
优点:可以求出
不为质数时的同余方程解。 -
缺点:和费马小定理差不多。
#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; }
费马小定理
-
优点:复杂度为
,处理较少元素逆元十分迅速。 -
缺点:当元素个数来到更高数量级时就不行了,或者
不为质数。
费马小定理,大致就是说
用快速幂处理即可。
递推预处理
-
优点:当需要处理元素在一个
能够处理的范围内时,可以快速求出它们的逆元。 -
缺点:当元素很大时无能为力,或者
不为质数。
证明自己翻书,代码如下:
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),即
0x61 性质
- 欧拉函数是积性函数,即当
时, 。 ,详细证明见 OI-wiki。- 详细见 OI-wiki。
0x62 欧拉定理
有个正整数
0x63 求 的欧拉函数值
可以在进行欧拉筛的同时计算出每个数的欧拉函数值。
// 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 海伦公式
简单来说,就是给出三角形三边长度
令
0xE3
#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; }
本文作者:wnsyou
本文链接:https://www.cnblogs.com/wnsyou-blog/p/math.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步