Skip to content

芥川龙之介的河童 - 题解

标签与难度

标签: 数论, 质数判断, 思维题, 暴力枚举, 最小公倍数 难度: 1500

题目大意喵~

主人你好呀,喵~ 荷取小姐姐需要我们的帮助!(๑•̀ㅂ•́)و✧

这道题是这样说的:给我们一个正整数 nn,我们需要找到一个最小的正整数 kk,让 kk 的阶乘(也就是 k!=1×2××kk! = 1 \times 2 \times \dots \times k)能够被从 11nn 的每一个整数 ii 整除。

简单来说,就是要找到最小的 kk,满足:

ik!(对于所有 i{1,2,,n})i \mid k! \quad (\text{对于所有 } i \in \{1, 2, \dots, n\})

然后把这个最小的 kk 输出就好啦,喵~

解题思路分析

喵哈哈,一看到这种“被所有数整除”的题目,我的猫猫直觉就告诉我,这和最小公倍数 (LCM) 有关系!

要想让 k!k! 同时被 1,2,,n1, 2, \dots, n 整除,其实就等价于让 k!k! 被它们的最小公倍数 lcm(1,2,,n)\text{lcm}(1, 2, \dots, n) 整除。所以问题就转化成了:找到最小的 kk,使得 lcm(1,2,,n)k!\text{lcm}(1, 2, \dots, n) \mid k!

不过,直接计算 lcm(1,2,,n)\text{lcm}(1, 2, \dots, n) 会非常非常大,可能会超出 long long 的范围,而且计算起来也很麻烦。我们得换个思路,从 kk 的性质入手,喵~

我们来分析一下,什么样的数 ii 是最“难”被 k!k! 整除的呢? 通常是那些比较大的质数,或者质数的高次幂,对吧?

  • 对于一个质数 pnp \le n:为了让 k!k! 能被 pp 整除, k!k! 的乘积因子中必须包含 pp。这就要求 kk 必须大于或等于 pp。所以,kk 至少要大于等于所有不超过 nn 的质数。也就是说,kk 至少得是不大于 nn 的最大质数(我们把它记作 PnP_n)。

这给了我们一个很强的下界:kPnk \ge P_n

那我们大胆猜想一下:答案会不会就是 PnP_n 呢?喵~ 让我们来验证一下!

k=Pnk = P_n。我们需要检查,对于所有 i{1,2,,n}i \in \{1, 2, \dots, n\}ii 是否都能整除 (Pn)!(P_n)!

  1. iPni \le P_n 时, ii 本身就是构成 (Pn)!(P_n)! 的因子之一,所以 (Pn)!(P_n)! 肯定能被 ii 整除。
  2. Pn<inP_n < i \le n 时,情况就变得有趣了!因为 PnP_n 是不大于 nn 的最大质数,所以这些 ii 必然是合数

既然 ii 是合数,我们就可以把它分解。

  • 如果 ii 可以分解成两个不相等的、都小于 ii 的数相乘,即 i=a×bi = a \times baba \ne b。由于 n<2Pnn < 2P_n (这是一个著名的数论结论,叫作伯特兰-切比雪夫定理,对于 n2n \ge 2 成立),可以推断出 aabb 都会小于 PnP_n。既然 aabb 都是小于 PnP_n 的不同整数,它们都会是 (Pn)!(P_n)! 的因子,所以它们的乘积 ii 也一定能整除 (Pn)!(P_n)!

  • 那什么时候这个猜想会失效呢?唯一的可能是当 ii 是一个质数的幂,形如 pap^a,并且 Pn<panP_n < p^a \le n。在这种情况下,pap^a 本身不是 (Pn)!(P_n)! 的直接因子,我们需要检查 (Pn)!(P_n)! 中质因子 pp 的数量是否足够多。

我们来测试几个例子:

  • n=10n=10: P10=7P_{10}=7。在 (7,10](7, 10] 之间的数有 8,9,108, 9, 10

    • 8=238=2^3。在 7!7! 中,有 2,4,62, 4, 6 这几个偶数,提供了 1+2+1=41+2+1=4 个因子 22242^4 能被 232^3 整除,所以 87!8 \mid 7!
    • 9=329=3^2。在 7!7! 中,有 3,63, 6 提供了 1+1=21+1=2 个因子 33323^2 能被 323^2 整除,所以 97!9 \mid 7!
    • 10=2×510=2 \times 52255 都小于 77,所以 107!10 \mid 7!
    • 看起来对于 n=10n=10,答案就是 k=7k=7
  • n=4n=4: P4=3P_4=3。在 (3,4](3, 4] 之间的数是 44

    • 4=224=2^2。我们需要检查 43!4 \mid 3! 是否成立。3!=63! = 666 不能被 44 整除。
    • 啊哈!猜想在这里失败了!喵呜~ (´;ω;`)
    • 这时候,因为 k=3k=3 不行,我们只能增大 kk。试试 k=4k=44!4! 显然能被 1,2,3,41, 2, 3, 4 整除。所以 n=4n=4 时,答案是 44

经过一番探索,我们发现这个“找最大质数”的策略只在 n=4n=4 这种极少数情况下会失效。实际上,可以证明,只有当 n4n \le 4 时,这个策略可能不完美。

  • n=1n=1, 答案是 11
  • n=2n=2, 答案是 22 (P2=2P_2=2)。
  • n=3n=3, 答案是 33 (P3=3P_3=3)。
  • n=4n=4, 答案是 44

所以,我们的最终策略就清晰啦!

  1. 如果 n4n \le 4,那么答案就是 nn 本身。(对于 n=1,2,3n=1,2,3 答案恰好是 nn 也是最大质数,但 n=4n=4 是特例,为了方便,直接把 n4n \le 4 的情况都当成答案是 nn 就好啦)。
  2. 如果 n>4n > 4,答案就是不大于 nn 的最大质数。

要找到这个最大质数,我们只需要从 nn 开始,一个一个往下递减检查,遇到的第一个质数就是我们想要的答案啦!

代码实现

下面就是我根据这个思路精心准备的代码啦,注释超详细的哦,喵~

cpp
#include <iostream>
#include <cmath>

// 检查一个数是不是质数的函数,喵~
// 质数是只能被1和它自己整除的大于1的自然数!
bool isPrime(int num) {
    // 小于等于1的数都不是质数哦
    if (num <= 1) {
        return false;
    }
    // 2是唯一的偶数质数,要特殊照顾一下
    if (num == 2) {
        return true;
    }
    // 如果是大于2的偶数,肯定不是质数
    if (num % 2 == 0) {
        return false;
    }
    // 从3开始,只检查奇数因子就够啦,一直检查到sqrt(num)
    // 因为如果num有比sqrt(num)大的因子,那它一定也有个比sqrt(num)小的因子
    int limit = sqrt(num);
    for (int i = 3; i <= limit; i += 2) {
        if (num % i == 0) {
            // 找到一个因子,说明它不是质数
            return false;
        }
    }
    // 经历了重重考验,它就是质数!
    return true;
}

void solve() {
    int n;
    std::cin >> n;

    // 根据我们的分析,对n <= 4的情况做特殊处理
    // n=1, k=1
    // n=2, k=2
    // n=3, k=3
    // n=4, k=4 (因为3! = 6,不能被4整除)
    // 实际上n=5时答案是5,也是最大质数,但并入下面的逻辑也一样。
    // 为了代码简洁,我们把 n<=3 的情况直接输出n,n=4也输出n。
    // 咦,这样的话,n<=4都输出n不就好了嘛!(代码里写n<=3只是因为n=4和>4的逻辑可以合并,但这样更清晰)
    if (n <= 3) {
        std::cout << n << "\n";
        return;
    }
    
    // 对于n >= 4的情况
    // 我们从n开始往下找,第一个遇到的质数就是答案!
    for (int k = n; k >= 1; --k) {
        if (isPrime(k)) {
            std::cout << k << "\n";
            break; // 找到就跑,因为我们要的是最大的那个质数
        }
    }
}

int main() {
    // 加速输入输出,让程序跑得更快,像猫猫一样敏捷!
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int t;
    std::cin >> t; // 读取测试用例的数量
    while (t--) {
        solve();
    }

    return 0;
}

复杂度分析

  • 时间复杂度: O(T×(gap)×n)O(T \times (\text{gap}) \times \sqrt{n}) 对于每个测试用例,我们从 nn 开始向下循环。设不大于 nn 的最大质数为 PnP_n,循环的次数为 nPn+1n - P_n + 1,这个差距(gap)在数论中被证明是很小的。在每次循环中,我们调用 isPrime 函数,其时间复杂度为 O(n)O(\sqrt{n})。所以总的时间复杂度是测试用例数 TT 乘以这个小的差距再乘以 n\sqrt{n}。在实际运行中,这个速度非常快!

  • 空间复杂度: O(1)O(1) 我们只用到了几个变量来存储输入和循环计数,没有使用额外的数组或者数据结构,所以空间消耗是常数级别的,非常节省内存,喵~

知识点总结

这道题表面上看起来很复杂,但通过观察和一点点数论知识的推理,就能把它简化成一个很经典的问题,真是有趣呢!

  1. 问题转化: 把“被 1n1 \dots n 的所有数整除”理解为“被 lcm(1,,n)\text{lcm}(1, \dots, n) 整除”是思考的第一步。
  2. 大胆猜想,小心求证: 猜想答案可能和最大质数有关,然后通过举例和逻辑分析来验证或修正猜想,是解决算法题的常用技巧。
  3. 特殊情况处理: 算法题中,边界和小数据范围的特殊情况常常是陷阱。像本题的 n=4n=4 就是一个关键的转折点。
  4. 质数判断: 高效地判断一个数是否为质数是数论问题的基础。试除法(检查到 n\sqrt{n})是其中最简单有效的方法之一。

希望这篇题解能帮助到你,主人!如果还有其他问题,随时都可以来问我哦,喵~ (ฅ'ω'ฅ)