题解:洛谷 P13014 最大公因数

【题目来源】

洛谷:P13014 [GESP202506 五级] 最大公因数 - 洛谷

【题目描述】

对于两个正整数 \(a,b\),他们的最大公因数记为 \(gcd(a,b)\)。对于 \(k>3\) 个正整数 \(c_1,c_2,…,c_k\),他们的最大公因数为:

\(gcd(c_1,c_2,…,c_k)=gcd(gcd(c_1,c_2,…,c_{k−1}),c_k)\)

给定 \(n\) 个正整数 \(a_1,a_2,…,a_n\) 以及 \(q\) 组询问。对于第 \(i(1≤i≤q)\) 组询问,请求出 \(a_1+i,a_2+i,…,a_n+i\) 的最大公因数,也即 \(gcd(a_1+i,a_2+i,…,a_n+i)\)

【输入】

第一行,两个正整数 \(n,q\),分别表示给定正整数的数量,以及询问组数。

第二行,\(n\) 个正整数 \(a_1,a_2,…,a_n\)

【输出】

输出共 \(q\) 行,第 \(i\) 行包含一个正整数,表示 \(a_1+i,a_2+i,…,a_n+i\) 的最大公因数。

【输入样例】

5 3
6 9 12 18 30

【输出样例】

1
1
3

【核心思想】

  1. 问题分析:给定 \(n\) 个正整数 \(a_1, a_2, \ldots, a_n\),对 \(q\) 组询问分别求 \(\gcd(a_1+i, a_2+i, \ldots, a_n+i)\)。直接每次计算 \(n\) 个数的 GCD 时间复杂度为 \(O(q \cdot n \cdot \log V)\),需要寻找数学规律优化。这是一个数论性质分析 + 周期性预处理问题,核心在于利用 GCD 的差分性质发现答案的周期性。

  2. 算法选择

    • GCD 差分性质\(\gcd(a_1+i, a_2+i, \ldots, a_n+i) = \gcd(a_1+i, a_2-a_1, a_3-a_1, \ldots, a_n-a_1)\),因此答案与 \(d = \gcd(a_2-a_1, a_3-a_1, \ldots, a_n-a_1)\) 相关
    • 周期性预处理:由于 \(\gcd(x, d) = \gcd(x \bmod d, d)\),答案具有周期 \(d\),只需预处理 \([1, d]\) 的结果
  3. 关键步骤

    • 读入与排序:读取 \(n, q\) 和数组 \(a\),排序以便计算差分
    • 计算差分 GCD\(t = \gcd_{i=2}^{n}(a_i - a_{i-1})\),若 \(t = 0\)(所有元素相同)则特判
    • 周期性预处理\(i\)\(1\)\(t\)):
      • \(b[i] = \gcd(a_1+i, a_2+i, \ldots, a_n+i)\)
    • 查询回答\(i\)\(1\)\(q\)):
      • 输出 \(b[cur]\)\(cur \leftarrow cur + 1\),若 \(cur > t\)\(cur \leftarrow 1\)
    • 输出结果
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n + n \log V + t \cdot n \log V + q)\),其中 \(t \leq \max(a_i)\)
    • 空间复杂度:\(O(n + t)\),存储数组和预处理结果
  5. 数论性质与周期性预处理的核心思想

    • GCD 的平移不变性\(\gcd(a_1+i, a_2+i, \ldots, a_n+i) = \gcd(a_1+i, a_2-a_1, a_3-a_1, \ldots, a_n-a_1)\),因为 \(\gcd(x, y) = \gcd(x, y-x)\),加上相同的 \(i\) 后差值不变
    • 周期性的来源:令 \(d = \gcd(a_2-a_1, \ldots, a_n-a_1)\),则 \(\gcd(a_1+i, d) = \gcd((a_1+i) \bmod d, d)\),当 \(i\) 增加 \(d\) 时,\((a_1+i) \bmod d\) 循环,因此答案周期为 \(d\)
    • 差分 GCD 的简化:只需计算相邻元素差分的 GCD 即可得到 \(d\),无需两两计算所有差值
    • 预处理查询优化:将 \(O(q \cdot n)\) 的在线计算转化为 \(O(t \cdot n)\) 的离线预处理和 \(O(1)\) 的查询回答
    • 适用于具有数学周期性、可通过数论性质优化的批量查询类问题

【算法标签】

普及- #约数

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;  // 定义数组最大长度

int n, q;              // n为数组长度,q为查询次数
int a[N], b[N];        // a存储原始数组,b存储预处理结果
int cur = 1;           // 当前查询位置指针
int t = 0;             // 存储数组元素的差分GCD

// 计算两个数的最大公约数
int gcd(int a, int b)
{
    if (b == 0)
        return a;
    return gcd(b, a % b);
}

int main()
{
    // 输入数组长度和查询次数
    cin >> n >> q;

    // 输入数组元素
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    // 对数组进行排序
    sort(a + 1, a + 1 + n);

    // 计算相邻元素的差分GCD
    for (int i = 2; i <= n; i++)
        t = gcd(t, a[i] - a[i - 1]);

    // 处理所有元素相同的情况(差分为0)
    if (t == 0)
    {
        for (int i = 1; i <= q; i++)
            cout << a[i] + i << endl;
        return 0;
    }

    // 预处理每个偏移量i的GCD结果
    for (int i = 1; i <= t; i++)
    {
        int g = 0;
        for (int j = 1; j <= n; j++)
            g = gcd(g, a[j] + i);
        b[i] = g;
    }

    // 处理查询
    for (int i = 1; i <= q; i++)
    {
        cout << b[cur] << endl;
        cur++;
        if (cur > t)
            cur = 1;  // 循环使用预处理结果
    }

    return 0;
}

【运行结果】

5 3
6 9 12 18 30
1
1
3
posted @ 2026-07-16 15:38  团爸讲算法  阅读(8)  评论(0)    收藏  举报