如果你以前从未接触过数论,AoPS 是一个很好的起点。

Resources
AoPS

练习题;请将训练主题设置为数论

AoPS

一本不错的入门书

学习资料

质因数分解

若非负整数 bb 能被正整数 aa 整除,即存在某个整数 kk 使得 b=kab=ka, 则称 aabb因数(或约数)。若整数 n>1n>1 的正因数只有 11nn,则称 nn质数。大于 11 且不是质数的整数称为合数

每个正整数都有唯一的质因数分解,即可以按照下式唯一地写成若干质数的乘积:

n=p1a1p2a2pkakn = {p_1}^{a_1} {p_2}^{a_2} \cdots {p_k}^{a_k}

其中 pip_i 是互不相同的质数,aia_i 是正整数。

下面讨论如何求任意正整数的质因数分解。

from typing import List
def factor(n: int) -> List[int]:
ret = []
i = 2
while i * i <= n:
while n % i == 0:
ret.append(i)
n //= i
i += 1
if n > 1:
ret.append(n)
return ret

该算法的时间复杂度为 O(n)\mathcal{O}(\sqrt n),因为 for 循环最多检查 n\sqrt n 个候选因数。虽然其中还有一个 while 循环,但每次用 ii 除去 nn 都会迅速减小 nn,从而让外层循环更早结束,并不会增加渐进复杂度。

n=252n=252 为例,算法执行过程如下:

iinnret\texttt{ret}
22252252{}\{\}
22126126{2}\{2\}
226363{2,2}\{2, 2\}
332121{2,2,3}\{2, 2, 3\}
3377{2,2,3,3}\{2, 2, 3, 3\}

此时 for 循环结束,因为 i=3i=3 已经大于 7\lfloor\sqrt7\rfloor。最后把 77 加入因数列表,否则它不会在循环中被加入。最终得到质因数分解 {2,2,3,3,7}\{2,2,3,3,7\}

Focus Problem – try your best to solve this problem before continuing!

题解——Counting Divisors

最直接的做法就是按题意计算:对每个 xx,在 O(x)\mathcal{O}(\sqrt x) 时间内 求出它的因数个数。

Warning!

由于 Python 的常数较大,下面的代码会在不少测试点上超时。

ans = []
for _ in range(int(input())):
div_num = 0
x = int(input())
i = 1
while i * i <= x:
if x % i == 0:
div_num += 1 if i**2 == x else 2
i += 1
ans.append(div_num)
print("\n".join(str(i) for i in ans))

这个解法的时间复杂度为 O(nx)\mathcal{O}(n\sqrt x),刚好足以通过。不过,我们 还可以把它优化到 O((x+n)logx)\mathcal{O}((x+n)\log x)

首先来看质因数分解的一个重要性质。设:

x=p1a1p2a2pkakx = {p_1}^{a_1} {p_2}^{a_2} \cdots {p_k}^{a_k}

那么 xx 的因数个数就是 (a1+1)(a2+1)(ak+1)(a_1+1)(a_2+1)\cdots(a_k+1)

这是因为在 xx 的任意因数中,pip_i 的指数只能取 [0,ai][0,a_i] 中的整数; 不同的指数选择会产生不同的因数,因此每个 pip_i 都为乘积贡献 ai+1a_i+1 种选择。

xx 最多有 O(logx)\mathcal{O}(\log x) 个不同质因数。因此,只要能高效求出 xx 的质因数分解,就可以利用上述性质在 O(logx)\mathcal{O}(\log x) 时间内回答一次 询问,而不再需要 O(x)\mathcal{O}(\sqrt x)

下面介绍如何用 O(xlogx)\mathcal{O}(x\log x) 的预处理,使每次质因数分解只需 O(logx)\mathcal{O}(\log x) 时间:

  1. 对每个 k106k\le 10^6,找出任意一个能整除 kk 的质数。可以使用 Sieve of Eratosthenes ,其复杂度为 O(nlogn)\mathcal{O}(n\log n),其中 nn 是考虑的最大值。筛法也有 线性时间版本,但这里并不需要。
  2. 不断用预处理出的质因数去除 xx,直到 x=1x=1,即可得到完整的质因数分解。

这一方法的实现如下:

MAX_N = 10**6
# max_div[i] contains the largest prime that can go into i
max_div = [0 for _ in range(MAX_N + 1)]
for i in range(2, MAX_N + 1):
if max_div[i] == 0:
for j in range(i, MAX_N + 1, i):
max_div[j] = i
ans = []

最大公因数与最小公倍数

最大公因数

两个整数 a,ba,b最大公因数(GCD),是同时作为二者因数的最大整数。 求两个非负整数的最大公因数时,可以使用欧几里得算法

gcd(a,b)={ab=0gcd(b,amodb)b0\gcd(a, b) = \begin{cases} a & b = 0 \\ \gcd(b, a \bmod b) & b \neq 0 \end{cases}

该算法可以递归实现如下:

def gcd(a: int, b: int) -> int:
return a if b == 0 else gcd(b, a % b)

比赛中通常无需自己实现,因为内置的 math 库提供了 gcdlcm 函数。

该函数的复杂度为 O(logab)\mathcal{O}(\log ab),因为 ab    b(moda)<b2a\le b\implies b\pmod a<\frac b2

欧几里得算法的最坏情况发生在 a,ba,b 是相邻斐波那契数 Fn,Fn+1F_n,F_{n+1} 时。 此时算法会依次计算 gcd(Fn,Fn+1)=gcd(Fn1,Fn)==gcd(0,F1)\gcd(F_n, F_{n + 1}) = \gcd(F_{n - 1}, F_n) = \dots = \gcd(0, F_1). ,总共需要 n+1n+1 次调用,与 log(FnFn+1)\log(F_nF_{n+1}) 成正比。

最小公倍数

两个整数 a,ba,b 的**最小公倍数(LCM)**是能同时被二者整除的最小正整数。 它可以利用最大公因数计算:

lcm(a,b)=abgcd(a,b)\operatorname{lcm}(a, b) = \frac{a \cdot b}{\gcd(a, b)}
Warning!

若直接写成 a * b / gcd(a, b),当 a * b 超出数据类型范围时可能发生整数 溢出(例如 C++ 和 Java 的 int 上限约为 20 亿)。应先计算 a / gcd(a, b),再乘以 b;只要最终结果可表示,这样就能避免中间溢出。

这两种运算都满足结合律。因此计算多个数的 GCD 或 LCM 时,可以按任意顺序 每次合并两个数。例如:

gcd(a1,a2,a3,a4)=gcd(a1,gcd(a2,gcd(a3,a4))).\gcd(a_1, a_2, a_3, a_4) = \gcd(a_1, \gcd(a_2, \gcd(a_3, a_4))).

欧拉函数

Focus Problem – try your best to solve this problem before continuing!

性质

欧拉函数记作 ϕ(n)\phi(n),表示区间 [1,n][1,n] 中与 nn 互质的正整数个数。 若 gcd(a,b)=1\gcd(a,b)=1,则称 a,ba,b 互质。

前 20 个正整数的 ϕ(n)\phi(n) 如下:

n1234567891011121314151617181920
ϕ(n)\phi(n)112242646410412688166188

欧拉函数是积性函数:当 gcd(n,m)=1\gcd(n,m)=1 时, ϕ(nm)=ϕ(n)ϕ(m)\phi(nm)=\phi(n)\phi(m)。例如 ϕ(15)=ϕ(35)=ϕ(3)ϕ(5)=24=8\phi(15)=\phi(3\cdot5)=\phi(3)\phi(5)=2\cdot4=8

来看 ϕ(n)\phi(n) 的几个特殊情况:

  • nn 是质数,则 ϕ(n)=n1\phi(n)=n-1,因为所有 1x<n1\le x<n 都满足 gcd(n,x)=1\gcd(n,x)=1
  • n=pqn=p^q 是质数幂,其中 pp 为质数且 q1q\ge1,则恰有 pq1p^{q-1} 个数 能被 pp 整除,因此 ϕ(pq)=pqpq1=pq1(p1)\phi(p^q)=p^{q} - p^{q-1} = p^{q-1}(p - 1)

利用积性和上述质数幂公式,可以由 nn 的质因数分解计算 ϕ(n)\phi(n)。设 n=p1q1p2q2pkqkn=p_1^{q_1}p_2^{q_2}\cdots p_k^{q_k},其中 pip_inn 的质因数,则:

ϕ(n)=ϕ(p1q1)ϕ(p2q2)ϕ(pkqk)=p1q11(p11)p2q21(p21)pkqk1(qk1)\phi(n)=\phi(p_1^{q_1}) \cdot \phi(p_2^{q_2}) \cdot \dots \cdot \phi(p_k^{q_k}) = p_1^{q_1-1}(p_1 - 1) \cdot p_2^{q_2-1}(p_2 - 1) \cdot \dots \cdot p_k^{q_k-1}(q_k - 1)

下面的实现在 O(n)\mathcal{O}(\sqrt n) 时间内分解质因数。ans -= ans / p 可能不太直观:设 ans=pqxans=p^qx,其中 pp 是质因数,xx 是其余部分。减去 ans/p=pq1xans/p=p^{q-1}x 后得到 pqxpq1x=pq1x(p1)p^qx-p^{q-1}x=p^{q-1}x(p-1),恰好对应上面的欧拉函数公式。

def phi(n: int) -> int:
ans = n
p = 2
while p * p <= n:
if n % p == 0:
while n % p == 0:
n //= p
ans -= ans // p
p += 1
if n > 1:
ans -= ans // n
return ans

题目中经常需要预处理 11nn 的所有欧拉函数值,此时逐个分解并不高效。 可以采用与埃氏筛相同的思想,时间复杂度为 O(NloglogN)\mathcal{O}(N\log\log N)

def precompute():
for i in range(1, MAX_N):
phi[i] = i
for i in range(2, MAX_N):
# If i is prime
if phi[i] == i:
for j in range(i, MAX_N, i):
phi[j] -= phi[j] // i

Focus Problem – try your best to solve this problem before continuing!

题解

题目要求计算所有满足 1i<jn1\le i<j\le n 的数对 (i,j)(i,j) 的 GCD 之和:

i=1nj=i+1ngcd(i,j).\sum_{i=1}^{n} \sum_{j=i+1}^{n} \gcd(i,j).

定义辅助函数 f(n)f(n),表示第二个元素固定为 nn 时的 GCD 之和:

f(n)=i=1ngcd(i,n).f(n) = \sum_{i=1}^{n} \gcd(i, n).

该函数对所有 ini\le ngcd(i,n)\gcd(i,n) 求和。当 gcd(i,n)=d\gcd(i,n)=d 时,必有 dnd\mid ngcd(i/d,n/d)=1\gcd(i/d,n/d)=1。满足条件的 ii 的个数为 ϕ(n/d)\phi(n/d)。因此可以把 f(n)f(n) 改写为对 nn 的因数求和:

f(n)=dndϕ(nd).f(n) = \sum_{d|n} d \cdot \phi\left(\frac{n}{d}\right).

我们可以高效计算所有 n106n\le10^6f(n)f(n)。不必逐个分解质因数,只需枚举 每个可能的因数 ii,再更新它的所有倍数 jj。对固定的 ii,向每个倍数 jjf(j)f(j) 中加入贡献 iϕ(j/i)i\phi(j/i)

最后,题目只统计 i<ji<j。而 f(j)f(j) 包含 i=ji=j 的情况(此时 gcd(j,j)=j\gcd(j,j)=j),必须将其减去。因此答案是调整后数值的前缀和:

ans[n]=j=1n(f(j)j).\text{ans}[n] = \sum_{j=1}^{n} (f(j) - j).

该预处理的总复杂度由调和级数界定: i=1NNiNlnN\sum_{i=1}^{N} \frac{N}{i} \approx N \ln N

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAX_N = 1e6;
ll phi[MAX_N + 1];
ll f[MAX_N + 1];
ll sum[MAX_N + 1];
int main() {

练习题

StatusSourceProblem NameDifficultyTags
ACEasy
Show TagsPrime Factorization
CFEasy
Show TagsDivisibility, Modular Arithmetic
CFEasy
Show TagsNumber Theory
CFEasy
Show TagsDivisibility
CSESEasy
Show TagsFunctional Graph, Prime Factorization
SPOJEasy
Show TagsDivisibility
CSESMedium
Show TagsDivisibility
CFMedium
Show TagsPrime Factorization
CCMedium
Show TagsDivisibility
CSESHard
Show TagsDivisibility
SPOJHard
Show TagsDivisibility, Euler Totient
CFHard
Show TagsDivisibility
ACHard
Show TagsDivisibility

Module Progress: