202603-有限不循环小数(luogu-P15798)

202603-有限不循环小数(luogu-P15798)

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

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

题目要求

题目描述

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

输入格式

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

输出格式

输出一行,包含一个整数,表示 LLRR 中终止数的数量。

输入输出样例 #1

输入 #1

2 11

输出 #1

5

说明/提示

样例解释

[2,11][2,11] 中,终止数有 224455881010

数据范围

保证 1LR1061 \le L \le R \le 10^6


题目分析

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

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

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

逻辑推导是这样的:

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

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

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

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

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

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

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

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

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

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

  • 22 的幂次 xx:因为 220=1048576>1062^{20} = 1048576 > 10^6,所以 xx00 枚举到 1919 就足够了。
  • 55 的幂次 yy:因为 58=3906255^8 = 39062559>1065^9 > 10^6,所以 yy00 枚举到 88 就足够了。

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


示例代码

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

#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;
}

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

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

本文由coderli.com原创,按照CC BY-NC-SA 4.0 进行授权

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

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

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

GESP/CSP认证交流QQ群: 688906745

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
最后更新于