NOI C++ 课程体系 / 数学基础篇 / 第4讲
CSP-J/S · 初等数论进阶 · m6-04

辗转相除法
与素数筛

从欧几里得算法到线性筛——掌握竞赛中最高效的GCD求解与质数批量生成技术!

6 大模块 3 种GCD变体 2 种筛法 5 大易错点 12 道分层练习
本讲 · 第 54/56 讲初等数论二:辗转相除法与素数筛
模块 01

情景导航

用故事建立直觉,用类比理解抽象算法。

故事欧几里得:2300年前的算法

公元前300年,古希腊数学家欧几里得在《几何原本》中记载了一种求最大公约数的方法:

用较大数除以较小数,再用余数去除除数,
如此反复,直到余数为零,最后的除数就是GCD。

这个算法被称为"辗转相除法"(Euclidean Algorithm),是人类历史上第一个被明确记录的算法。

2300多年后的今天,它依然是计算机程序中求GCD的标准实现,时间复杂度仅为 O(log n)。

而它的扩展版本——扩展欧几里得算法,更是解不定方程、求乘法逆元的核心工具。

💡 为什么筛法如此重要?

在竞赛中,经常需要处理 [2, N] 范围内所有质数。逐个判定是 O(N√N),而埃氏筛只需 O(N log log N),线性筛更是 O(N)。

当 N = 10⁷ 时,暴力需要约 10¹⁰ 次运算(超时),而线性筛只需 10⁷ 次(毫秒级)。

①生活类比:GCD与筛无处不在
🎹

音乐节拍 = GCD

两种节奏3拍和4拍同步,需LCM(3,4)=12拍。

→ GCD/LCM的实际意义
🧱

铺地砖 = GCD

房间长宽分别为a,b,最大正方形砖边长=GCD(a,b)。

→ GCD的几何解释
🔍

筛选简历 = 筛法

HR按条件逐轮淘汰不合格者,留下精英。

→ 筛法的"排除"思想
🔐

RSA密钥 = EXGCD

求模逆元需要扩展欧几里得算法。

→ 密码学的数学基础
②编程定义:本讲知识地图

本讲是 m6-03 的进阶深化,聚焦GCD的高级应用与筛法的工程实现。

📌 本讲核心知识地图

板块核心内容难度
GCD深度性质证明、复杂度分析、边界情况⭐⭐
扩展欧几里得ax+by=gcd(a,b) 求解、逆元⭐⭐⭐
LCM应用先除后乘、多数LCM、周期问题⭐⭐
埃氏筛法原理、优化、O(N log log N)⭐⭐
线性筛最小质因子、break条件、O(N)⭐⭐⭐
筛法对比时间/空间/适用场景选择⭐⭐
③核心口诀
🔑 口诀一:辗转相除

大除小取余,余替大继续
余零停手时,小数即GCD

🔑 口诀二:扩展欧几里得

递归到底返回 gcd 和 (1,0)
回溯时 x=y', y=x'-⌊a/b⌋×y'

🔑 口诀三:线性筛灵魂

每个合数只被最小质因子筛一次
i % primes[j] == 0 时必须 break

模块 02

知识探究

深入理解GCD的本质、扩展欧几里得算法与两种筛法的工程实现。

1GCD 深度剖析 核心

核心定理:GCD(a, b) = GCD(b, a mod b),其中 a ≥ b > 0。

📜 正确性证明
  • 设 d = GCD(a, b)
  • 则 d|a 且 d|b
  • a mod b = a - ⌊a/b⌋×b
  • d 也整除 (a mod b)
  • 所以 d 是 b 和 (a mod b) 的公约数
  • 反之亦然 → GCD相等
⏱️ 复杂度分析
  • Lamé定理:步数 ≤ 5×位数
  • 最坏情况:相邻斐波那契数
  • F(45)≈10⁹ → 最多约45步
  • 时间复杂度:O(log min(a,b))
  • 实际运行极快,可视为常数

🔍 边界情况与性质:

性质公式说明
零值GCD(a, 0) = a递归终止条件
相等GCD(a, a) = a一步终止
互质GCD(a, b) = 1无公共质因子
倍数GCD(a, ka) = ak为任意正整数
线性组合GCD(a,b) = GCD(a, b+ka)k为任意整数
多数GCDGCD(a,b,c) = GCD(GCD(a,b),c)可递推扩展
C++三种GCD实现方式
// 方式1:递归(经典)
int gcd_rec(int a, int b) {
    return b == 0 ? a : gcd_rec(b, a % b);
}

// 方式2:迭代(避免栈溢出)
int gcd_iter(int a, int b) {
    while (b != 0) {
        int t = b;
        b = a % b;
        a = t;
    }
    return a;
}

// 方式3:C++17 内置(推荐)
#include <numeric>
auto g = std::gcd(a, b);  // C++17
2扩展欧几里得算法 (EXGCD) 必考

📜 贝祖等式(Bézout's Identity)

对于任意整数 a, b,存在整数 x, y 使得:

a × x + b × y = GCD(a, b)

扩展欧几里得算法不仅求出 GCD,还同时求出满足上式的 (x, y)。

🖐 手推追踪:EXGCD(30, 12)

1
30 = 2×12 + 6 → 递归 EXGCD(12, 6)
a=30, b=12, q=⌊30/12⌋=2
2
12 = 2×6 + 0 → 递归 EXGCD(6, 0)
a=12, b=6, q=⌊12/6⌋=2
3
基准情形:GCD=6, x=1, y=0
6×1 + 0×0 = 6 ✅
4
回溯第2层:x'=1, y'=0 → x=0, y=1-2×0=1
验证:12×0 + 6×1 = 6 ✅
5
回溯第1层:x'=0, y'=1 → x=1, y=0-2×1=-2
验证:30×1 + 12×(-2) = 30-24 = 6 ✅
C++扩展欧几里得算法
// 返回 gcd(a,b),并通过引用返回 x, y
int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1; y = 0;
        return a;
    }
    int x1, y1;
    int g = exgcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return g;
}
🔑 EXGCD 三大应用

① 解不定方程 ax + by = c 有解 ⟺ GCD(a,b) | c
② 求乘法逆元:若 GCD(a,m)=1,则 exgcd(a,m,x,y) 中的 x 即为 a⁻¹ mod m
③ 中国剩余定理的基础组件

3LCM 应用技巧 实用
⚠️

LCM计算的致命陷阱

❌ 错误写法
lcm = a * b / gcd(a,b);
a*b 可能先溢出!
例:a=b=10⁹ → a*b=10¹⁸ > INT_MAX
✅ 正确写法
lcm = a / gcd(a,b) * b;
先除后乘,a/gcd 一定整除
结果不超 long long 范围

多数LCM:

C++多数LCM递推
long long lcm(long long a, long long b) {
    return a / __gcd(a, b) * b;
}

// 多数LCM:逐步合并
long long result = arr[0];
for (int i = 1; i < n; i++) {
    result = lcm(result, arr[i]);
    if (result > LIMIT) break; // 防溢出提前退出
}
💡 注意:多数LCM增长极快!几个不大的数的LCM就可能超出 long long。竞赛中常需判断是否超限。
4埃氏筛法详解 基础

核心思想:从2开始,将每个质数的所有倍数标记为合数。未被标记的即为质数。

C++埃氏筛法(含优化)
const int MAXN = 10000000;
bool is_comp[MAXN];      // false=质数, true=合数
vector<int> primes;

void eratosthenes() {
    is_comp[0] = is_comp[1] = true;
    for (int i = 2; i < MAXN; i++) {
        if (!is_comp[i]) {
            primes.push_back(i);
            // 优化:从 i*i 开始标记(i*k, k<i 已被更小质因子标记过)
            if ((long long)i * i < MAXN) {
                for (int j = i * i; j < MAXN; j += i)
                    is_comp[j] = true;
            }
        }
    }
}
⏱️ 时间复杂度
  • O(N log log N)
  • N=10⁷ 时约 0.1秒
  • N=10⁸ 时约 1~2秒
  • 对大多数题目足够
💾 空间优化
  • bool数组:1字节/元素
  • bitset:1位/元素
  • 节省8倍内存
  • N=10⁸ 仅需 ~12MB
⚡ 关键优化
  • 从 i² 开始标记
  • 跳过偶数(特判2)
  • 只用奇数索引
  • 速度提升约2倍
5线性筛(欧拉筛) 进阶

核心思想:保证每个合数恰好被其最小质因子筛掉一次,从而实现严格 O(N)。

C++线性筛(欧拉筛)标准模板
const int MAXN = 10000000;
bool is_comp[MAXN];
vector<int> primes;

void linear_sieve() {
    for (int i = 2; i < MAXN; i++) {
        if (!is_comp[i])
            primes.push_back(i);

for (int j = 0; j < (int)primes.size() 
             && (long long)i * primes[j] < MAXN; j++) {

is_comp[i * primes[j]] = true;

// 🔑 核心:保证最小质因子筛
            if (i % primes[j] == 0) break;
        }
    }
}

💡 为什么 if (i % primes[j] == 0) break; 是正确的?

当 i % primes[j] == 0 时,说明 primes[j] 是 i 的最小质因子。

此时 i × primes[j] 的最小质因子就是 primes[j],由当前 i 筛掉是正确的。

但如果继续用 primes[j+1] 去筛 i × primes[j+1]:

由于 primes[j] | i,所以 primes[j] | (i × primes[j+1]),

而 primes[j] < primes[j+1],说明 i × primes[j+1] 的最小质因子是 primes[j] 而非 primes[j+1]。

如果不 break,这个合数将来会被 (i×primes[j+1]/primes[j]) 用 primes[j] 再筛一次 → 重复标记!

break 保证了每个合数只被最小质因子筛一次 → O(N)。

6两种筛法对比 选型
对比维度埃氏筛法线性筛(欧拉筛)
时间复杂度O(N log log N)O(N)
N=10⁷ 耗时~50ms~30ms
N=10⁸ 耗时~800ms~400ms
代码复杂度简单(5行核心)稍复杂(需理解break)
合数标记次数可能被多次标记恰好一次
附加功能仅筛质数可同时求最小质因子、μ(n)、φ(n)
推荐场景N≤10⁷,快速编码N≥10⁷,或需要积性函数
🔑 选型建议

① CSP-J:埃氏筛完全够用,优先写简单的
② CSP-S/NOIP:建议掌握线性筛,部分题必须用
③ 需要 μ(n)/φ(n):只能用线性筛
④ 不确定时:写线性筛,向下兼容

模块 03

实践应用

通过详细的手推追踪,巩固EXGCD、LCM和筛法。

题1EXGCD 求解不定方程

题目:求方程 35x + 15y = 10 的一组整数解。

查看参考答案
1
先求 GCD(35, 15)
35 = 2×15 + 5
15 = 3×5 + 0 → GCD = 5
2
检查是否有解
10 ÷ 5 = 2,整除 → 有解
3
EXGCD(35,15) 求特解
35×1 + 15×(-2) = 5
即 x₀=1, y₀=-2 对应 GCD=5
4
缩放得到原方程的解
等式两边 ×(10/5)=2:
x = 1×2 = 2, y = -2×2 = -4

验证:35×2 + 15×(-4) = 70 - 60 = 10 ✅

通解:x = 2 + 3t, y = -4 - 7t(t为任意整数)

题2LCM 周期问题

题目:甲每3天去一次图书馆,乙每5天去一次,丙每7天去一次。三人今天同时去了,下次同时去是几天后?

查看参考答案

分析:求 LCM(3, 5, 7)

3, 5, 7 两两互质 → LCM = 3 × 5 × 7 = 105

答案:105天后三人再次同时去图书馆。

注意计算顺序(防溢出):

LCM(3,5) = 3/GCD(3,5)×5 = 3/1×5 = 15

LCM(15,7) = 15/GCD(15,7)×7 = 15/1×7 = 105

题3筛法综合应用

题目:求 [2, 100] 内所有质数的个数,以及最大的质数。

查看参考答案

使用埃氏筛法:

标记2的倍数(从4开始):4,6,8,...,100

标记3的倍数(从9开始):9,15,21,...,99

标记5的倍数(从25开始):25,35,55,...,95

标记7的倍数(从49开始):49,77,91

下一个质数11,11²=121>100,停止。

结果:

质数个数 = 25 个

最大质数 = 97

完整列表:2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97

模块 04

总结提升

1核心要点总结
知识板块必须记住的核心
GCDGCD(a,b)=GCD(b,a%b);O(log n);GCD(a,0)=a
EXGCDax+by=GCD(a,b);回溯公式 x=y', y=x'-⌊a/b⌋y'
LCMa/GCD(a,b)*b(先除后乘);多数递推
埃氏筛O(N log log N);从i²开始标记;简单高效
线性筛O(N);i%p[j]==0时break;最小质因子唯一筛
选型N≤10⁷用埃氏筛;N≥10⁷或需积性函数用线性筛
2拓展:裴蜀定理与逆元

💡 裴蜀定理(Bézout's Theorem)

方程 ax + by = c 有整数解 ⟺ GCD(a,b) | c

特别地,ax + by = 1 有解 ⟺ GCD(a,b) = 1(即a,b互质)

乘法逆元:

若 GCD(a, m) = 1,则存在 a⁻¹ 使得 a × a⁻¹ ≡ 1 (mod m)

用 EXGCD 求:exgcd(a, m, x, y) → x 即为 a⁻¹ mod m

若 x 为负数,调整为 ((x % m) + m) % m

3思考与启发

💡 学完本讲,你应该掌握的6项能力

1. 能手推辗转相除法全过程,理解 O(log n) 的来源。

2. 能写出扩展欧几里得代码,并用它解不定方程。

3. 会用 EXGCD 求乘法逆元,处理模意义下的除法。

4. 计算LCM时始终先除后乘,防止溢出。

5. 能写出埃氏筛和线性筛的标准模板。

6. 理解线性筛中 break 的正确性,能向他人解释。

模块 05

学习检测

5 个高频易错点,每个都有口诀帮你记住。

易错1

LCM 先乘后除导致溢出

❌ 错误
a * b / gcd(a,b)
a*b 先算,可能溢出!
✅ 正确
a / gcd(a,b) * b
先除后乘,安全!
口诀:LCM先除再乘,顺序反了爆long long!
易错2

EXGCD 回溯公式记反

❌ 错误
x = x1 - (a/b)*y1;
y = y1;
x和y的赋值搞反了!
✅ 正确
x = y1;
y = x1 - (a/b)*y1;
x拿的是子问题的y'!
口诀:x拿y',y拿x'减商乘y'!
易错3

线性筛忘记 break

❌ 错误
省略 if(i%p[j]==0) break;
退化为 O(N log N),失去线性优势
✅ 正确
if (i % primes[j] == 0) break;
保证最小质因子唯一筛 → O(N)
口诀:线性筛没break,就不是线性筛!
易错4

埃氏筛从 2i 开始标记

❌ 低效
for(j=2*i; j<N; j+=i)
2i,3i,...,(i-1)i 已被更小质因子标记过
✅ 优化
for(j=i*i; j<N; j+=i)
从 i² 开始,减少无效标记
口诀:埃氏筛从i²起,之前倍数已处理!
易错5

EXGCD 求逆元忘记调正

❌ 错误
inv = x;
x可能为负数!
如 exgcd(3,7,x,y) → x=-2
✅ 正确
inv = ((x%m)+m)%m;
-2 → ((-2%7)+7)%7 = 5
3×5=15≡1(mod 7) ✅
口诀:逆元可能负,加m取模保正数!
速查GCD与筛法速查表
知识点关键数据/结论
GCDGCD(a,b)=GCD(b,a%b);O(log min(a,b))
EXGCDax+by=gcd;x=y', y=x'-⌊a/b⌋y'
LCMa/gcd(a,b)*b(先除后乘)
逆元exgcd(a,m,x,y) → inv=((x%m)+m)%m
裴蜀定理ax+by=c 有解 ⟺ gcd(a,b)|c
埃氏筛O(N log log N);从i²开始标记
线性筛O(N);i%p[j]==0 → break
Lamé定理GCD步数 ≤ 5×较小数位数
📌知识点笔记卡

🔄 辗转相除

GCD(a,b)=GCD(b,a%b)

O(log n),Lamé定理

GCD(a,0)=a 终止

🧮 EXGCD

ax+by=gcd(a,b)

x=y', y=x'-⌊a/b⌋y'

求逆元、解不定方程

📐 LCM

a/gcd*b 先除后乘

多数递推合并

增长极快防溢出

🔍 埃氏筛

O(N log log N)

从i²开始标记

简单好用够CSP-J

⚡ 线性筛

O(N) 严格线性

i%p[j]==0 → break

最小质因子唯一筛

🔐 逆元

a⁻¹ mod m

exgcd求x再调正

((x%m)+m)%m

课后练习

分层练习作业

A基础 / B提高 / C挑战,难度递增,点击展开答案与解析。

🟢 A组 · 基础(4题)

A1选择题

GCD(48, 36) = ()

A. 6   B. 12   C. 18   D. 24

查看参考答案

答案:B. 12

48%36=12, 36%12=0 → GCD=12。

考点:辗转相除法依据 gcd(a,b) = gcd(b, a%b),不断把大数换小,直到余数为 0,此时上一步的除数就是最大公约数。

易错:把递推写成 gcd(a,b)=gcd(b,a/b)(丢掉了取余),或在余数为 1 时误以为已经结束。

A2选择题

LCM(6, 8) = ()

A. 2   B. 24   C. 48   D. 12

查看参考答案

答案:B. 24

GCD(6,8)=2, LCM=6/2×8=3×8=24。

考点:求最小公倍数优先使用公式 LCM(a,b) = a / GCD(a,b) * b,先除后乘兼顾正确性与安全性。

易错:直接算 6×8=48,或想当然地把较大的 8 当作 LCM。

A3选择题

埃氏筛法的时间复杂度是()

A. O(N)   B. O(N log N)   C. O(N log log N)   D. O(N√N)

查看参考答案

答案:C. O(N log log N)

这是埃氏筛的经典复杂度,接近线性但略慢。

考点:每个质数 p 要划掉约 N/p 个数,把所有质数的 N/p 相加约为 N·ln ln N,这就是埃氏筛的经典复杂度。

易错:与线性筛的 O(N) 混淆,或误记成暴力枚举的 O(N²)。

A4选择题

在线性筛中,if (i % primes[j] == 0) break; 的作用是()

A. 防止数组越界
B. 加速循环
C. 保证每个合数只被最小质因子筛一次
D. 跳过偶数

查看参考答案

答案:C

这是线性筛的核心,保证O(N)复杂度的关键。

考点:当 i 能被当前最小质因子 primes[j] 整除时立刻 break,可保证每个合数只被自己的最小质因子标记一次,这是 O(N) 的关键。

易错:以为 break 是为了跳过偶数或减少内存占用,误解了它「防止重复标记」的本意。

🟡 B组 · 提高(4题)

B1填空题

用辗转相除法求 GCD(1071, 462),需要______步除法运算。

查看参考答案
C++完整参考代码
#include <iostream>
using namespace std;

int main() {
    int a = 1071, b = 462;             // 求 GCD(1071, 462)
    int steps = 0;

    while (b != 0) {                   // 余数不为 0 就继续
        int r = a % b;                 // 取余数
        cout << a << " / " << b << " = " << a / b
             << " 余 " << r << endl;
        a = b;                         // 除数变成被除数
        b = r;                         // 余数变成除数
        steps++;                       // 做了一次除法
    }

    cout << "共 " << steps << " 次除法,GCD = " << a << endl;   // 3 次,GCD = 21
    return 0;
}

答案:共 3 步除法,GCD(1071, 462) = 21。过程:1071 ÷ 462 = 2 余 147 → 462 ÷ 147 = 3 余 21 → 147 ÷ 21 = 7 余 0。

思路:辗转相除法(欧几里得算法)的核心性质是 gcd(a, b) = gcd(b, a mod b)。每做一次"a ÷ b 取余",就把除数挪到被除数的位置、余数挪到除数的位置,直到余数变成 0,此时除数就是最大公约数。本题数了 3 次除法:1071/462、462/147、147/21,第三次余数为 0,循环结束,答案是 21。

考点:辗转相除法(欧几里得算法)的原理与实现、a % b 取余、循环不变式。

易错:① 数步数时要明白"最后余 0 的那次也算一次除法",所以是 3 步不是 2 步;② 交换顺序不能写反:先 a = b 再 b = r,如果先写 b = r 就会把还需要的旧 b 覆盖掉(写成 int r = a % b; 提前存好余数就没问题);③ 边界情况:若两个数中有 0,GCD 就是另一个数,此时循环一次都不执行,答案直接是 a。

B2选择题

EXGCD(12, 8, x, y) 执行后,x 和 y 的值分别是()

A. x=1, y=-1   B. x=-1, y=2   C. x=1, y=-2   D. x=-1, y=1

查看参考答案

答案:A. x=1, y=-1

GCD(12,8)=4。验证:12×1+8×(-1)=12-8=4 ✅

考点:扩展欧几里得算法在辗转相除求 GCD 的同时回推系数,给出一组满足 ax + by = gcd(a,b) 的整数解。

易错:把 x、y 当成 12 和 8 的因数(如 3 和 2),或忘记 x、y 中通常有一个是负数。

B3选择题

3 在模 7 下的乘法逆元是()

A. 2   B. 3   C. 4   D. 5

查看参考答案

答案:D. 5

3×5=15≡1(mod 7)。或用EXGCD:exgcd(3,7,x,y)→x=-2→((-2%7)+7)%7=5。

考点:乘法逆元就是满足 a × x ≡ 1 (mod m) 的 x,模数很小时直接试乘 1~m-1 最快。

易错:把逆元当成 1/3 ≈ 0.33,或误用 7 - 3 = 4 之类与定义无关的计算。

B4选择题

用埃氏筛求 [2,30] 内的质数,被标记为合数的个数是()

A. 18   B. 19   C. 20   D. 21

查看参考答案

答案:B. 19

[2,30]共29个数,质数10个(2,3,5,7,11,13,17,19,23,29),合数=29-10=19。

考点:区间内合数个数可由「总数 − 质数个数」得到,而 [2,30] 中不含 1,所以总数就是 29 个。

易错:把 1 也算进合数,或漏数 29 导致质数个数算成 9 个。

🔴 C组 · 挑战(4题)

C1初赛真题

方程 6x + 9y = 15 的一组整数解是()

A. x=1, y=1   B. x=2, y=-1   C. x=-1, y=3   D. 以上都是

查看参考答案

答案:A(x=1, y=1)

GCD(6,9)=3, 15/3=5 → 有解。

A: 6+9=15 ✅; B: 12-9=3... 不对。重新验算:

B: 6×2+9×(-1)=12-9=3≠15 ❌

C: 6×(-1)+9×3=-6+27=21≠15 ❌

只有A正确,答案为 A。(注:此题需仔细验算每个选项)

考点:二元一次不定方程 ax+by=c 有整数解的充要条件是 gcd(a,b) 能整除 c,满足后再逐一验算选项即可。

易错:只验证一个选项就作答,或不知道一个解可加 (b/g, -a/g) 生成其他解而误判选项唯一。

C2初赛真题

线性筛中,当 i=12 时,内层循环会依次用哪些质数去筛?(已知 primes={2,3,5,7,11})

A. 2,3   B. 2,3,5   C. 2   D. 2,3,5,7

查看参考答案

答案:C. 2

j=0: 12×2=24, 标记。12%2==0 → break!

等等,12%2==0 在第一次就break了?那答案是C?

重新分析:12%2==0 → break,只用了质数2。

答案为 C. 2。

易错:以为内层循环会一直用质数表里的所有质数去筛,忽略 12 % 2 == 0 在第一次循环就触发 break。

C3初赛真题

若 GCD(a,b)=1,则 LCM(a,b) = ()

A. 1   B. a+b   C. a×b   D. max(a,b)

查看参考答案

答案:C. a×b

LCM = a/GCD(a,b)×b = a/1×b = a×b。互质时LCM等于乘积。

考点:把 LCM(a,b) = a / GCD(a,b) * b 中的 GCD 取 1,即得特例 LCM(a,b) = a × b。

易错:以为互质时 LCM 仍要再除以 2,或误把 LCM 当成 a + b。

C4初赛真题

下列关于辗转相除法的说法,错误的是()

A. GCD(a,b) = GCD(b, a mod b)
B. 最坏情况输入是相邻斐波那契数
C. 时间复杂度为 O(√n)
D. GCD(a,0) = a

查看参考答案

答案:C

辗转相除法的时间复杂度是 O(log n),不是 O(√n)。O(√n) 是试除法的复杂度。

考点:每做两次取模,新数值至少减半,所以辗转相除的步数与数的位数同阶,复杂度为 O(log n)。

易错:与试除法 O(√n) 混淆,或错记成埃氏筛的 O(N log log N)。