Skip to content

喵~ 主人,今天我们来看一道有趣的数论题哦~ 这道题叫做 "Vasya and Petya's Game",听起来就像是两个小伙伴在玩耍,让本喵也来掺一脚,用我们聪明的头脑帮帮他们吧!

题目大意

这道题是说呀,有两个小伙伴 Vasya 和 Petya 在玩一个猜数字的游戏,喵~ Vasya 心里想一个 1 到 n 之间的数字 x,Petya 的任务就是把它猜出来。

Petya 可以问很多问题,问题的格式都是:“你心里想的那个数 x 能不能被 y 整除呀?”。Petya 可以一次性问完所有他想问的问题,然后 Vasya 会对每个问题回答 'yes' 或者 'no'。

Petya 需要根据这些回答,百分百确定 Vasya 心里想的数字是几。我们的任务就是,帮助 Petya 找到一个最少需要问多少个问题,以及具体问哪些数 y,才能保证一定能猜对,呐~


解题思路

要保证能猜对,意思就是对于 1 到 n 之间的任意两个不同的数,Petya 的问题列表必须能把它们区分开来,对吧?

比如说,如果 n 是 4,数字可能是 1, 2, 3, 4。我们怎么区分它们呢?

靠的就是整除性质,而一个数的整除性质,完全是由它的质因数分解决定的哦!这就是大名鼎鼎的算术基本定理,喵~ 任何一个大于1的整数,都能被唯一地分解成一堆质数的乘积。

比如 12 = 2^2 * 3^1。只要我们知道了这个数有哪些质因子,以及每个质因子的幂次是多少,我们就能唯一确定这个数了!

所以,我们的目标就变成了:通过提问,确定目标数字 x 的质因数分解形式!

对于每一个小于等于 n 的质数 p,我们需要知道 x 的因子中 p 的最高次幂是多少。比如说,我们要知道 x 是被 2^1 整除,还是 2^2,还是 2^3...?

我们可以这样问嘛:

  • "x 能被 p 整除吗?"
  • "x 能被 p^2 整除吗?"
  • "x 能被 p^3 整除吗?" ... 一直问到 p^k > n 为止。

举个栗子,如果 n=10,我们要确定 x 的因子中 2 的幂次。我们可以问关于 2, 4, 8 的问题。

  • 如果 Vasya 对 2, 4, 8 的回答都是 'no',那 x 的质因子中没有 2。
  • 如果对 2 回答 'yes',但对 4 回答 'no',那 x 的质因子中 2 的幂次就是 1。
  • 如果对 2, 4 回答 'yes',但对 8 回答 'no',那 x 的质因子中 2 的幂次就是 2。
  • 如果对 2, 4, 8 都回答 'yes',那 x 的质因子中 2 的幂次就是 3。

对所有小于等于 n 的质数 p 都这么来一遍,我们就能拼出 x 完整的质因数分解,从而确定 x 的值啦!

所以,我们需要问的问题集合,就是所有小于等于 n 的质数 p 的所有次幂 p^k (其中 p^k <= n)。这个集合就是能区分 1 到 n 所有数字的最小问题集,喵~

总结一下本喵的思路就是:

  1. 找到 1 到 n 之间所有的质数。
  2. 对于每个找到的质数 p,把它所有的次幂 (p, p^2, p^3, ...) 只要不超过 n,都加到我们的问题列表里。
  3. 最后把这个列表打印出来就好啦!是不是很简单呀?

题解

下面就是本喵根据这个思路写出的 C++ 代码啦,主人请看~

cpp
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    // 加上这两行可以让输入输出变得飞快,就像猫咪追激光笔一样,咻~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n;
    std::cin >> n; // 读取 n 的值,喵

    // 使用埃拉托斯特尼筛法来找出 1 到 n 所有的质数,就像猫咪标记领地一样~
    std::vector<bool> is_prime(n + 1, true); // 一开始假设所有数都是质数
    if (n >= 0) {
        is_prime[0] = false; // 0 不是质数哦
    }
    if (n >= 1) {
        is_prime[1] = false; // 1 也不是质数哦
    }

    // 开始筛选!
    for (int p = 2; p * p <= n; ++p) {
        // 如果 p 是一个还没被划掉的质数
        if (is_prime[p]) {
            // 那就把 p 的所有倍数都划掉,因为它们肯定是合数啦
            for (int i = p * p; i <= n; i += p) {
                is_prime[i] = false;
            }
        }
    }

    // 准备一个容器,来装我们所有要问的问题
    std::vector<int> questions;
    // 遍历 2 到 n
    for (int p = 2; p <= n; ++p) {
        // 如果 p 是一个质数
        if (is_prime[p]) {
            // 就把它所有的次幂 (p, p^2, p^3...) 都加到问题列表里
            long long current_power = p;
            while (current_power <= n) {
                questions.push_back(static_cast<int>(current_power));
                // 计算下一个次幂,要小心不要溢出哦,不过 long long 在这里足够了
                current_power *= p;
            }
        }
    }

    // 最后,把结果打印出来~
    std::cout << questions.size() << std::endl; // 先打印问题的总数
    for (size_t i = 0; i < questions.size(); ++i) {
        // 打印每个问题,用空格隔开
        std::cout << questions[i] << (i == questions.size() - 1 ? "" : " ");
    }
    std::cout << std::endl;

    return 0; // 任务完成,喵~
}

知识点介绍

这道题里面用到了两个非常重要的数论知识点哦,本喵来给主人科普一下!

1. 算术基本定理 (The Fundamental Theorem of Arithmetic)

这个定理是数论的基石,喵~ 它的内容是:任何一个大于 1 的自然数,要么它本身就是一个质数,要么它可以被分解为一串质数的乘积,并且这种分解方式是唯一的(不考虑顺序的话)。

比如说,数字 72: 72 = 8 * 9 = (2 * 2 * 2) * (3 * 3) = 2^3 * 3^2 除了 2 和 3,没有别的质数能整除 72 了。而且,2 的幂次一定是 3,3 的幂次一定是 2,不能多也不能少。这就是唯一的分解!

正是因为这个定理,我们才有了通过确定质因数分解来确定原数的想法,呐。

2. 埃拉托斯特尼筛法 (Sieve of Eratosthenes)

这是一个古老又高效的找出一定范围内所有质数的方法,名字听起来很厉害,但原理超级简单,就像小猫玩毛线球一样,滚来滚去就把事情做完啦!

它的步骤是这样的:

  1. 先创建一个列表,包含从 2 到 n 的所有整数,并假设它们都是质数。
  2. 从第一个质数 p=2 开始。
  3. p 的所有倍数 (2p, 3p, 4p, ...) 都从列表里划掉(标记为合数),因为它们肯定不是质数了。
  4. 然后找到下一个没被划掉的数,这个数就是下一个质数(比如 3)。重复第 3 步,划掉它的所有倍数。
  5. 一直重复这个过程,直到我们检查的数 p 的平方大于 n 为止。

最后,列表里所有没被划掉的数,就都是质数啦!这个方法比一个一个去试除判断是不是质数要快得多,非常适合解决这道题,喵~

好啦,这次的题解就到这里啦!希望本喵的讲解能帮到主人哦~ 下次再见,喵呜~ (ฅ'ω'ฅ)

Released under the MIT License.