Skip to content

喵~ 各位同学好呀!今天本猫娘要给大家带来 Codeforces 上一道非常经典又可爱的题目——C. Common Divisors 的题解分析哦!

这道题就像是在一大堆毛线球里,找出能完美缠绕所有毛线球的线芯一样,是不是很有趣呢?那么,就跟着我一起来看看怎么解决它吧,喵~

题目大意

这道题的要求其实很简单哦,听我慢慢道来~

题目会给我们一个包含 n 个正整数的数组 a。我们的任务就是,找到到底有多少个正整数 x,这个 x 能够整除数组 a 中的 每一个 数字。

换句话说,就是要我们找出数组 a 中所有元素的 公共约数 的数量。

举个例子吧,喵~ 如果数组是 a = [2, 4, 6, 2, 10]

  • 1 可以整除 2, 4, 6, 2, 10。
  • 2 可以整除 2, 4, 6, 2, 10。
  • 其他像 3, 4 这样的数就不能整除所有的数了。

所以,公共约数就是 12,总共有 2 个。我们的答案就是 2 啦!

解题思路

看到这个问题,大家的第一反应可能是:呜哇,要检查每一个数对数组里所有元素的关系,会不会很麻烦呀?

别担心!这里有一个非常重要的数学性质,能让问题瞬间变得简单起来,就像猫咪找到了最舒服的纸箱一样!

核心思想: 一个数组中所有元素的 公共约数 集合,等价于这些元素 最大公约数 (GCD) 的约数 集合。

是不是感觉眼前一亮,喵?也就是说,我们根本不需要去关心数组里那 n 个复杂的数字,我们只需要关心它们的「最大公约数」这一个数就行了!

所以,我们的解题步骤就清晰地分成了两步:

  1. 第一步:求出数组中所有元素的最大公约数 (GCD)。 我们可以先计算第一个和第二个数的 GCD,得到一个结果 g。然后用这个 g 和第三个数计算新的 GCD,再用新的结果和第四个数计算……以此类推,像滚雪球一样,滚到最后,得到的就是整个数组所有元素的 GCD 啦。

  2. 第二步:计算这个 GCD 的约数个数。 当第一步完成后,问题就转化成了一个更简单的问题:“求一个数 g 有多少个约数”。这个就简单多啦,对吧?

代码解说

下面我们来看看具体的 C++ 代码是怎么实现这个思路的,喵~

cpp
#include <iostream>
#include <numeric> // For std::gcd

// The main idea is that the set of common divisors of a list of numbers
// is identical to the set of divisors of their greatest common divisor (GCD).
// So, the problem breaks down into two steps:
// 1. Calculate the GCD of all numbers in the input array.
// 2. Count the number of divisors of the resulting GCD.

int main() {
    // Fast I/O for performance
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n;
    std::cin >> n;

    // The values of a_i can be up to 10^12, so we must use a 64-bit integer type.
    long long g;
    std::cin >> g;

    // Step 1: Calculate the GCD of all numbers in the array.
    // We can do this iteratively. Initialize g with the first element,
    // then for each subsequent element, update g to be the GCD of the current g
    // and the new element. std::gcd is available since C++17.
    for (int i = 1; i < n; ++i) {
        long long a;
        std::cin >> a;
        g = std::gcd(g, a);
    }

    // Step 2: Count the number of divisors of the resulting GCD 'g'.
    // A naive loop from 1 to g would be too slow since g can be up to 10^12.
    // An efficient method is to iterate from 1 up to the square root of g.
    long long divisor_count = 0;
    // The loop variable 'i' must be long long to prevent overflow in 'i * i'.
    for (long long i = 1; i * i <= g; ++i) {
        if (g % i == 0) {
            // If 'i' is a divisor, then 'g / i' is also a divisor.
            if (i * i == g) {
                // If 'i' is the square root of g, it's a single divisor.
                // We count it just once to avoid duplication.
                divisor_count++;
            } else {
                // Otherwise, 'i' and 'g / i' form a pair of distinct divisors.
                divisor_count += 2;
            }
        }
    }

    std::cout << divisor_count << std::endl;

    return 0;
}
  1. 输入和类型

    • std::ios_base::sync_with_stdio(false); std::cin.tie(NULL); 这两行是为了加速 C++ 的输入输出,在处理大量数据时是个好习惯,喵~
    • long long g; 题目中 a_i 的值最大可以到 10^12,普通的 int 类型是存不下的,会溢出!所以我们必须用 long long 来存储 GCD 和数组中的元素。
  2. 计算 GCD

    • std::cin >> g; 先读入第一个数,作为 GCD 的初始值。
    • for (int i = 1; i < n; ++i) 循环从第二个数开始,依次读入。
    • g = std::gcd(g, a); 这一行是核心!std::gcd 是 C++17 标准库里提供的函数,可以直接计算两个数的最大公约数。我们用它来不断更新 g 的值,最终得到的 g 就是所有元素的 GCD。
  3. 计算约数个数

    • 这里的 g 可能非常大(最大 10^12),如果我们从 1 循环到 g 来找约数,肯定会超时。所以我们用了一个聪明的办法!
    • for (long long i = 1; i * i <= g; ++i) 我们只循环到 g 的平方根。为什么呢?因为约数都是成对出现的!如果 ig 的约数,那么 g / i 也一定是 g 的约数。
    • if (g % i == 0) 判断 i 是否为约数。
    • if (i * i == g) 这是一个特殊情况。如果 g 是一个完全平方数(比如 36),那么它的平方根(比如 6)和 g 除以它的平方根是同一个数。这个时候,我们只找到了一个约数,所以 divisor_count 只加 1。
    • else { divisor_count += 2; } 如果不是完全平方数,那么 ig / i 就是一对不同的约数,所以 divisor_count 直接加 2。

这样一来,我们就能高效地解决问题啦!

知识点介绍

这道题涉及了两个非常基础但重要的数论知识点,本猫娘来给大家科普一下~

1. 最大公约数 (Greatest Common Divisor, GCD)

  • 定义:最大公约数是指,在两个或多个整数的所有公共约数中,最大的那一个。例如,12 和 18 的公共约数有 1, 2, 3, 6,其中最大的就是 6,所以 gcd(12, 18) = 6
  • 重要性质:GCD 满足结合律,即 gcd(a, b, c) = gcd(gcd(a, b), c)。正是这个性质,让我们能够通过迭代的方式,两两计算,最终求得整个数组的 GCD。

2. 高效地求一个数的约数个数

对于一个数 N,如何快速求出它有多少个约数呢?

  • 朴素方法:从 1 循环到 N,检查每个数 i 是否能整除 N。当 N 很大时(比如 10^12),这个方法太慢了,会超时。
  • 平方根优化法
    • 约数是成对出现的。如果 iN 的约数,那么 N / i 也必然是 N 的约数。例如,对于 N=302 是约数,那么 30/2=15 也是约数;3 是约数,30/3=10 也是。
    • 我们可以只遍历从 1sqrt(N) 的数 i
    • 每当我们找到一个约数 i,我们就同时找到了另一个约数 N / i。所以约数个数 +2
    • 特别注意:如果 N 是一个完全平方数,比如 N=36,当 i=6 时,iN/i 是同一个数。为了避免重复计数,这种情况只能算作一个约数,个数 +1

好啦,今天的解题小课堂就到这里了,喵~ 希望大家通过这道题,对 GCD 和约数计数有了更深的理解!我们下次再见啦!

Released under the MIT License.