数论全家桶(一)
RpInfinity · · 算法·理论
本文使用了 DeepSeek 进行查错。
本文为个人对于数论一部分知识点的整理,由于作者能力有限,可能会有错误或者不给出严格证明的情况,敬请谅解,欢迎提出修正,我会尽量修改,文中的代码不保证正确性。
Upd on 2026-09-21:修改了符号错误。
质数
对于
判断质数
由于质数的因子只有
bool check(int p) {
for (int i = 2; i < p; ++i)
if (p % i == 0) return false;
return true;
}
注意到任意因子
bool check(int p) {
if (p < 2) return false;
for (int i = 2; (long long)i * i <= p; ++i)
if (p % i == 0) return false;
return true;
}
注意到这个方法如果要求出区间内所有的质数是非常慢的。
质数筛
质数筛用于解决区间内筛质数的问题。
考虑到对于每一个
如何优化?对于每一个质数
现在的做法对于每一个合数还是会有多次标记的情况,我们应当使每个合数只被它最小的质因子标记。
可以改成下面这样:
设
当我们外层枚举到
- 当
P<p 时,因为m 没有小于p 的质因子,所以P\nmid m ,不会停止; - 当
P=p 时,标记i\cdot P=mp=n 为合数; - 然后检查
i\bmod P 。如果i\bmod p=0 就停止枚举更大的质数。因为此时p\mid i ,对于更大的质数q>p ,iq 的最小质因子仍然是p ,应该留给以后用p 去筛,否则会重复。
如果
这就是欧拉筛,正确性显然,我们设值域为
int prime[M], idx;
bitset<N + 5> isp;
void euler() {
isp.set(), isp[0] = isp[1] = 0;
for (int i = 2; i <= N; ++i) {
if (isp[i]) prime[++idx] = i;
for (int j = 1; j <= idx && (long long)i * prime[j] <= N; ++j) {
isp[i * prime[j]] = false;
if (i % prime[j] == 0) break;
}
}
}
这种方法只能筛
注意到前文说过每个合数
int prime[N], idx;
bitset<N> isp;
void euler(int n) {
isp.set();
isp[0] = isp[1] = 0;
for (int i = 2; i <= n; ++i) {
if (isp[i]) prime[++idx] = i;
for (int j = 1; j <= idx && (ll)i * prime[j] <= n; ++j) {
isp[i * prime[j]] = 0;
if (i % prime[j] == 0) break;
}
}
}
int main() {
ll L, R; cin >> L >> R;
euler(sqrtl(R) + 1);
vector<bool> ok(R - L + 1, true);
if (L <= 1) ok[0] = false;
for (int i = 1; i <= idx; ++i) {
ll p = prime[i];
ll st = max(p * p, (L + p - 1) / p * p);
for (ll j = st; j <= R; j += p)
ok[j - L] = false;
}
}
时间复杂度为
整除
设
有趣的性质:
-
-
- 若
a\mid b 且a\mid c ,则对任意整数m,n ,a\mid (mb+nc) ; - 若
a\mid b ,则对于\forall c\in\mathbb{Z} ,均有a\mid bc ; - 若
a\mid b 且c\mid d ,则ac\mid bd ; -
- 若
a,b\in\mathbb{Z}(a,b\neq 0) 那么有a\mid b,b\mid a \Rightarrow a=\pm b ; - 若
a\mid b 且b\ne 0 ,则|a|\le |b| ,因此非零整数的约数只有有限个; - 若
a\mid b ,则\gcd(a,b)=|a| ; - 若
a\mid bc,\quad \gcd(a,b)=1 ,则a\mid c ; - 若
a\mid c,\quad b\mid c,\quad \gcd(a,b)=1 ,则ab\mid c ; - 对于任意两个不相等的整数
a,b 和正整数n ,都有(a - b)\mid (a^n - b^n) ,可以因式分解(a^n - b^n) 易发现其有(a-b) 这个因式,对于任意d\mid n ,还有a^d - b^d\mid a^n - b^n ,正确性易得; - 同理若
n 为奇数,则有a + b\mid a^n + b^n ,否则若n 为偶数,则a + b\mid a^n - b^n 。
当然还有很多有趣的性质,这里就不再列举。
同余
设
-
枚举,直接从
a,b 较小值从大到小枚举,复杂度为O(\min(a,b)) ; -
枚举较小数的因子,复杂度大概是
O(\sqrt{\min(a,b)}) ; -
辗转相减法(更相减损法)(默认
a,b 为正整数):对于正整数
a,b :- 若
a = b ,则\gcd(a,b) = a ,结束; - 若
a>b ,则a\gets a-b ; - 若
a < b ,则b\gets b-a 。
证明:设
d = \gcd(a, b) ,则d\mid a 且d\mid b ,于是d\mid (a - b) 。因此d 为a-b 与b 的公因数,所以d\leq\gcd(a-b, b) 。反过来,设e = \gcd(a - b, b) ,则e\mid a - b 且e\mid b ,于是e\mid a ,所以e 为a 和b 的公约数,所以e\leq\gcd(a, b) 。综上
\gcd(a, b) = \gcd(a - b, b) 。int gcd(int a, int b) { if (!a) return b; if (!b) return a; while (a != b) { if (a > b) a -= b; else b -= a; } return a; }适用于高精度取模不方便时。
- 若
-
辗转相除法:
注意到辗转相减法会多次重复的减去同一个数,造成了时间的浪费,故将减操作改为取模,此时时间复杂度达到
O(\log\min(a,b)) 。
然后还有个东西叫 Binary GCD(Stein 算法),利用奇偶性优化更相减损法:
- 若
a,b 均为偶数,则\gcd(a,b)=2\gcd(a/2,b/2) ; - 若一奇一偶,把偶数除以
2 ,\gcd 不变; - 若均为奇数,则用更相减损法得到
|a-b| 与较小者,此时结果一定为偶数,可继续除以2 。
最小公倍数
设
形式化地:
当
性质:
-
\operatorname{lcm}(a,b)=\operatorname{lcm}(b,a) -
\operatorname{lcm}(a,b)=\operatorname{lcm}(|a|,|b|) -
若
\gcd(a,b)=1 ,则\operatorname{lcm}(a,b)=|ab| -
对任意正整数
a,b ,\gcd(a,b)\cdot \operatorname{lcm}(a,b)=|ab| ,由此可得:\operatorname{lcm}(a,b)=\frac{|ab|}{\gcd(a,b)} -
若
a=\prod p_i^{\alpha_i} ,b=\prod p_i^{\beta_i} ,则\operatorname{lcm}(a,b)=\prod p_i^{\max(\alpha_i,\beta_i)} 即每个质因子的指数取两者中的较大值。
剩余系
同余类(剩余类)
固定模数
这个集合称为模
完全剩余系
从模
性质:
若
- 它们模
m 两两不同余; - 任意整数
a 满足\gcd(a, m) = 1 ,ar_1, ar_2, \dots, ar_m 也是完全剩余系; - 更一般地,若
\gcd(a, m) = 1 ,ar_i + b 也构成完全剩余系。
简化剩余系
只从那些与
性质:
若
- 每个
r_i 与m 互质; - 它们模
m 两两不同余; - 若
\gcd(a, m) = 1 ,则ar_1, ar_2, \dots, ar_{\varphi(m)} 仍然构成一个模m 的一个缩系。
欧拉函数
欧拉函数
其中
性质:
-
若
p 是质数,则\varphi(p) = p - 1 ; -
若
p 是质数,k\in\mathbb{Z}^+(k\geq 1) ,则\varphi(p^k) = p^k - p^{k - 1} = p^{k - 1}(p - 1) ; -
欧拉函数为积性函数,即若
\gcd(m, n) = 1 ,则\varphi(mn) = \varphi(m)\varphi(n) ; -
公式:若
n = \prod^{r}_{i = 1}p_i^{\alpha_i}, 其中
p_i 为互不相同的质数,则\varphi(n) = n\prod^r_{i=1}(1 - \frac{1}{p_i}) = \prod_{i=1}^r p_i^{\alpha_i - 1}(p_i - 1).
欧拉定理
若
其中
证明:取模
设整数
也构成模
把
由于每个
求欧拉函数
如果要求一个数
进行计算,时间复杂度为质因数分解的复杂度,为
但要求一个
考虑欧拉函数的性质。
- 若
\gcd(a,b) = 1 ,则\varphi(ab) = \varphi(a)\varphi(b) ; - 其次对于质数
p ,有\varphi(p) = p - 1 ; - 对于质数幂
\varphi(p^k) = p^{k - 1}(p - 1) 。
如果只是用积性,我们只能处理互质的两个数相乘;
但合数
欧拉筛恰好解决了这个问题。欧拉筛中,每个合数
-
若
p\nmid i ,则\gcd(i,p)=1 ,由积性得\varphi(i\cdot p)=\varphi(i)\cdot\varphi(p)=\varphi(i)\cdot(p-1) -
若
p\mid i ,则p 是i 的最小质因子,i\cdot p 与i 的质因子集合完全相同,只是p 的指数多了1 。由公式\varphi(n)=n\prod_{q\mid n}\left(1-\frac1q\right) 可知
\frac{\varphi(i\cdot p)}{i\cdot p}=\frac{\varphi(i)}{i} 所以此时
我们就在欧拉筛的同时递推出了每个数的欧拉函数,时间复杂度
代码:
int phi[N], prime[N], cnt;
bool vis[N];
void get_phi(int n) {
phi[1] = 1;
for (int i = 2; i <= n; ++i) {
if (!vis[i]) {
prime[++cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= cnt && (ll)i * prime[j] <= n; ++j) {
int p = prime[j];
vis[i * p] = true;
if (i % p == 0) {
phi[i * p] = phi[i] * p; break;
} else phi[i * p] = phi[i] * (p - 1);
}
}
}
费马小定理
对于一个质数
证明很简单,注意到
裴蜀定理
设
即
以下给出两种证明:
-
取集合
S = \{ax + by\mid x,y\in\mathbb Z, ax+by > 0\}. 显然
S 非空,于是由良序原理,S 中有最小正元素,设其为d = ax_0 + by_0 。先证明
d\mid a 且d\mid b :假设
d\nmid a ,那么有a = qd + r(0<r<d) ,那么r = a - qd ,而d = ax_0 + by_0 ,代入得到:r = a - q(ax_0 + by_0) = a(1 - qx_0) + b(-qy_0) 这里
1-qx_0 和-qy_0 显然均为整数,所以r\in S ,但是有前提易知r<d ,与d 为S 中最小元素矛盾,故d\mid a ,同理可证得d\mid b 。所以
d 为a,b 的公因数,所以d\leq \gcd(a,b) 。同时,因为
\gcd(a,b)\mid a 且\gcd(a,b)\mid b ,所以\gcd(a,b)\mid ax_0 + by_0 = d ,所以\gcd(a,b)\leq d 。综上所述,
d = \gcd(a,b) 。 -
原命题可以转化为此命题:
\gcd(a,b) 能表示成a,b 的整系数线性组合。设当前两个数为
u,v ,并且已知u=sa+tb,\qquad v=pa+qb 其中
s,t,p,q\in\mathbb Z 。辗转相减做的是:
u-v=(s-p)a+(t-q)b 或者
v-u=(p-s)a+(q-t)b 系数仍然是整数。一直是
a,b 的整系数线性组合。最终的
\gcd 等于两者之一,显然也是a,b 的整系数组合,故原命题成立。
那么对于
若
扩展欧几里得算法
用于求解方程
欧几里得算法即为前文的辗转相除法,其基于:
递归时,假设我们已经求出了
我们将
代入上式,得到
整理一下得到
对比原式可以发现
当递归到
int exgcd(int a, int b, int &x, int &y) {
if (b == 0) {
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
迭代版:
int exgcd(int a, int b, int &x, int &y) {
int x1 = 1, y1 = 0, x2 = 0, y2 = 1;
while (b) {
int q = a / b;
int t = a % b;
a = b; b = t;
int tx = x1 - q * x2;
int ty = y1 - q * y2;
x1 = x2; y1 = y2;
x2 = tx; y2 = ty;
}
x = x1; y = y1;
return a;
}
有了特解,求通解就很容易了,设特解为
其中
求解线性同余方程
考虑一个线性同余方程
其中
由定义,其等价于存在
显然可以通过扩欧求解。
逆元
设
则称
此时也说
-
e_i\equiv 1\pmod {m_i}.
先看第一个
其中
那么第一个就满足了,但是直接令
我们并不知道
显然
那么我们只需要将
我们设
并且这个解在模
唯一性的证明:
假设方程组的解在模
以及
两式相减可以得到
所以
又因为
假设不成立,所以解在模
扩展中国剩余定理
我们将原问题进一步推广,考虑模数不两两互质的情况。
此时 CRT 的构造方法不再适用,因为
一次性处理不了这个问题,那就将其分成子问题。每次只合并两个方程,把两个方程等价地转化成一个新的同余方程。反复合并,直到只剩一个方程,就得到了原方程组的解。
考虑两个方程
由第一个方程,
令
若有解,两边除以
此时
代回
对于
其中
ll exCRT(int k, ll a[], ll m[]) {
ll A = a[1], M = m[1];
for (int i = 2; i <= k; ++i) {
ll d, x, y;
d = exgcd(M, m[i], x, y);
if ((a[i] - A) % d) return -1;
ll t = (a[i] - A) / d * x % (m[i] / d);
A += M * t;
M = M / d * m[i];
A = (A % M + M) % M;
}
return A;
}
这第一篇先写到这吧,喜欢的点个赞吧。