文章

【GESP】C++五级真题 luogu-P15798, [GESP202603 五级] 有限不循环小数

2026年3月,GESP五级真题,考察数论基础(质因数分解特性)枚举算法,难度⭐⭐★☆☆。洛谷难度级别:普及-

P15798 [GESP202603 五级] 有限不循环小数

题目要求

题目描述

若 $\frac{1}{a}$ 可化为一个有限的,不循环的小数,则称 $a$ 为终止数。 请你求出在 $L$ 到 $R$ 中终止数的数量。

输入格式

输入一行,包含两个整数 $L,R$。

输出格式

输出一行,包含一个整数,表示 $L$ 到 $R$ 中终止数的数量。

输入输出样例 #1

输入 #1

1
2 11

输出 #1

1
5

说明/提示

样例解释

在 $[2,11]$ 中,终止数有 $2$、$4$、$5$、$8$、$10$。

数据范围

保证 $1 \le L \le R \le 10^6$。


题目分析

这道题目虽然披着”小数”的外衣,本质上是一道数论题。在 GESP 五级考试中,数论基础(如质数判断、质因数分解、最大公约数等)是高频重点。

数学原理破解:什么样的分数能化成有限且不循环的小数?

我们在小学数学中学过:一个最简分数能不能化成有限小数,完全取决于它的分母 $a$。由于我们使用的数学计数法是十进制(逢十进一),而 $10 = 2 \times 5$。

逻辑推导是这样的:

假设 $\frac{1}{a}$ 可以化为有限小数,这意味着它一定可以写成 $\frac{k}{10^n}$ 的形式(其中 $k$ 和 $n$ 都是正整数)。 由 $\frac{1}{a} = \frac{k}{10^n}$,我们可以通过交叉相乘推导出 $a \times k = 10^n$。 因为 $10^n = (2 \times 5)^n = 2^n \times 5^n$,即 $10^n$ 这个数字的全部质因数只有 $2$ 和 $5$。 既然 $a$ 是 $10^n$ 的一个约数(因为 $a$ 乘以整数 $k$ 等于 $10^n$),那么 $a$ 身上包含的质因数必定也只能是 $2$ 或 $5$,绝不可能含有 $3, 7, 11$ 等其它任何质数。

因此,当且仅当一个数 $a$ 的质因数只包含 $2$ 和 $5$ 时,$\frac{1}{a}$ 才能被除尽,化为有限且不循环的小数。 也就是说,所谓”终止数” $a$,一定满足特定的数学构造形式:$a = 2^x \times 5^y$(其中 $x$ 和 $y$ 是非负整数,即 $x \ge 0, y \ge 0$),并且 $a$ 必须在题目要求的范围之内。

算法思路:如何找到所有终止数?

有了上面的数学结论,接下来就是怎么在 $[L, R]$ 的范围内找到所有满足条件的数。这里提供两种思路:

思路一:逐个检验法(直接分解)

最直观的想法:从 $L$ 到 $R$,每个数都拿过来”查户口”。对于当前数 $a$,不停地除以 $2$(只要能整除就除),再不停地除以 $5$(只要能整除就除),把 $2$ 和 $5$ 的因子全部剥干净。如果最后剩下的数等于 $1$,说明 $a$ 的质因数只有 $2$ 和 $5$,它就是”终止数”;否则说明它还含有其他质因数,不合格。

这个方法逻辑简单清晰,容易理解和编写。时间复杂度为 $O((R - L) \times \log R)$,在 $R \le 10^6$ 的数据范围内完全够用。

思路二:主动生成法(枚举幂次)

换一个角度——与其挨个排查,不如直接按照公式把所有终止数”造”出来

既然终止数的形式是 $a = 2^x \times 5^y$,我们只需要用两层循环枚举 $x$ 和 $y$ 的值:

  • $2$ 的幂次 $x$:因为 $2^{20} = 1048576 > 10^6$,所以 $x$ 从 $0$ 枚举到 $19$ 就足够了。
  • $5$ 的幂次 $y$:因为 $5^8 = 390625$,$5^9 > 10^6$,所以 $y$ 从 $0$ 枚举到 $8$ 就足够了。

这样总共只有大约 $20 \times 9 = 180$ 次判定,效率极高。最后判断生成的 $a$ 是否落在 $[L, R]$ 内即可。


示例代码

思路一:逐个检验法(直接分解)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

// 判断一个数是否为"终止数"
// 核心逻辑:不断除以 2 和 5,看最终能否除尽变成 1
bool isTerminating(int a) {
    // 先把所有的因子 2 除干净
    while (a % 2 == 0) {
        a /= 2;
    }
    // 再把所有的因子 5 除干净
    while (a % 5 == 0) {
        a /= 5;
    }
    // 如果剩下的是 1,说明 a 的质因数只有 2 和 5
    return a == 1;
}

int main() {
    int L, R;
    std::cin >> L >> R;

    int ans = 0; // 用于统计符合要求的"终止数"的个数

    // 从 L 到 R,逐个检验每个数是否为终止数
    for (int i = L; i <= R; i++) {
        if (isTerminating(i)) {
            ans++;
        }
    }

    // 输出最终结果
    std::cout << ans << std::endl;

    return 0;
}

思路二:主动生成法(枚举幂次)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>

int main() {
    long long L, R;
    std::cin >> L >> R;

    int ans = 0; // 用于统计符合要求的"终止数"的个数

    // 外层循环枚举 2 的幂次,变量 p2 用来存放 2^x 的值
    // p2 不断乘以 2,直到超出 R 的最大范围
    for (long long p2 = 1; p2 <= R; p2 *= 2) {

        // 内层循环枚举 5 的幂次,变量 p5 用来存放 5^y 的值
        // p5 不断乘以 5,同时我们要确保 p2 * p5 的总积不能超过 R
        for (long long p5 = 1; p2 * p5 <= R; p5 *= 5) {

            // 组装出当前的 a 值
            long long a = p2 * p5;

            // 如果这个生成的 a 值刚好落在 [L, R] 范围内,计数加一
            if (a >= L && a <= R) {
                ans++;
            }
        }
    }

    // 最终输出满足条件的数量
    std::cout << ans << std::endl;

    return 0;
}

代码解析小贴士

  1. 思路一的核心技巧——”剥洋葱”: 判断一个数的质因数是否只包含 $2$ 和 $5$,不需要做完整的质因数分解。只要用 while 循环反复除以 $2$、再反复除以 $5$,最后看结果是不是 $1$。如果是 $1$,说明”洋葱”已经被剥光了,质因数确实只有 $2$ 和 $5$。这个技巧在数论题中非常实用。
  2. 思路二的效率优势——”查户口”不如”自己生”: 当在一个大范围里寻找具备极少特征的特殊数字时,从头到尾挨个排查虽然可行但比较笨拙。把特殊的数字按照生成规则反向拼装创造出来,往往能将时间复杂度从 $O(N \log N)$ 降到 $O(\log^2 N)$。
  3. 乘风破浪的 long long 在思路二中,尽管题目给定 $R \le 10^6$ 还在 int 承受范围内,但是在写底数倍增循环(如 p2 *= 2, p5 *= 5)时,下一次循环乘后的预判值极容易越界爆出 int 的上限导致死循环或错误。在处理所有涉及连乘倍增的题目时,养成使用 long long 的好习惯。

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

GESP 学习专题站:GESP WIKI

luogu-”系列题目可在洛谷题库进行在线评测。

bcqm-”系列题目可在编程启蒙题库进行在线评测。

欢迎加入Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答

欢迎加入C++ GESP/CSP认证学习QQ频道,考试资源总结汇总

欢迎加入C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
本文由作者按照 CC BY-NC-SA 4.0 进行授权