题解:学而思编程 区间外最大公约数

【题目来源】

学而思编程:区间外最大公约数

【题目描述】

给定一个长度为 \(n\) 的数列:\(a_1,a_2,\dots, a_n\)。现在有 \(q\) 个问题,其中第 \(i\) 个问题描述为:给定一个区间 \([l_i,r_i]\),请你计算不包含字段\(a_{l_i},\dots,a_{r_i}\) 的其他数字的最大公约数是多少?

例如:假设数列为 \(6,7,5,91,4,12\),那么区间 \([2,4]\) 以外的最大公约数为 \(gcd\{6,4,12\}=2\)

【输入】

第一行两个正整数 \(n\)\(q\)

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

接着 \(q\) 行每行两个正整数 \(l_i,r_i\)

【输出】

\(q\) 行,每行一个整数表示结果,保证有解。

【输入样例】

10 4
12 24 8 9 27 6 8 15 35 49
1 8
2 9
3 10
5 8

【输出样例】

7
1
12
1

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数列 \(a_1, a_2, \ldots, a_n\)\(q\) 次询问区间 \([l, r]\) 以外的所有元素的最大公约数。这是一个前缀 GCD + 后缀 GCD问题,关键在于利用 GCD 的结合律,将区间外元素的 GCD 分解为前缀和后缀两部分。

  2. 算法选择

    • 前缀 GCD 数组lgcd[i] = gcd(a_1, a_2, \ldots, a_i),表示前 \(i\) 个元素的 GCD
    • 后缀 GCD 数组rgcd[i] = gcd(a_i, a_{i+1}, \ldots, a_n),表示从第 \(i\) 个到第 \(n\) 个元素的 GCD
    • GCD 结合律:区间 \([l, r]\) 以外的 GCD = \(gcd(\text{前 } l-1 \text{ 个}, \text{后 } n-r \text{ 个}) = gcd(lgcd[l-1], rgcd[r+1])\)
  3. 关键步骤

    • 初始化:读取 \(n\)(数列长度)和 \(q\)(询问次数),读取数组 \(a[1..n]\)
    • 预处理前缀 GCD
      • lgcd[0] = 0\(gcd(0, x) = x\),作为单位元)
      • 遍历 \(i\)\(1\)\(n\)
        • lgcd[i] = gcd(lgcd[i-1], a_i):前 \(i\) 个元素的 GCD
    • 预处理后缀 GCD
      • rgcd[n+1] = 0
      • 遍历 \(i\)\(n\)\(1\)
        • rgcd[i] = gcd(rgcd[i+1], a_i):从第 \(i\) 个到第 \(n\) 个元素的 GCD
    • 处理 \(q\) 次询问
      • 读取区间 \([l, r]\)
      • ans = gcd(lgcd[l-1], rgcd[r+1]):前缀 \([1, l-1]\) 与后缀 \([r+1, n]\) 的 GCD
      • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log V + q \log V)\)\(V\) 为值域,预处理 \(O(n)\) 次 GCD,每次询问 \(O(1)\)
    • 空间复杂度:\(O(n)\),前缀 GCD 数组 lgcd[0..n] 和后缀 GCD 数组 rgcd[1..n+1]
  5. 前缀后缀分解的核心思想

    • GCD 结合律\(gcd(A \cup B) = gcd(gcd(A), gcd(B))\),区间外元素天然分为前缀 \([1, l-1]\) 和后缀 \([r+1, n]\) 两部分,两部分无交集
    • 离线预处理:前缀和后缀 GCD 与询问无关,可预先计算,使每次查询降为 \(O(1)\)
    • 边界处理lgcd[0] = 0rgcd[n+1] = 0 作为单位元,保证 \(l = 1\)\(r = n\) 时公式依然成立(\(gcd(0, x) = x\)
    • 查询独立性:每次询问只涉及两个预计算值的 GCD,与区间长度无关
    • 适用于"区间外统计 + 可结合运算"类问题,前缀后缀分解将 \(O(n)\) 的区间查询优化到 \(O(1)\)

【解题思路】

【算法标签】

约数

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int a[100005];
int lgcd[100005];
int rgcd[100005];
int gcd(int a, int b)
{
    if (b==0)
        return a;
    return gcd(b, a%b);
}
int main()
{
    int n, q; cin >> n >> q;
    // 下标Wie0的那个a[0]没有用 结合 gcd特点
    for (int i=1; i<=n; i++)
        cin >> a[i];
    // 预处理操作 lgcd[i]前i个数的最大公约数
    for (int i=1; i<=n; i++)
        lgcd[i] = gcd(lgcd[i-1], a[i]);
    // rgcd[i] 后n-i+1个数的最大公约数
    for (int i=n; i>=1; i--)
        rgcd[i] = gcd(rgcd[i+1], a[i]);

    while (q--) {
        int l, r; cin >> l >> r;
        cout << gcd(lgcd[l-1], rgcd[r+1]) << endl;
    }
    return 0;
}

【运行结果】

6 4
7 14 3 6 18 9
1 3
3
1 4
9
2 5
1
3 6
7
posted @ 2026-07-04 14:52  团爸讲算法  阅读(5)  评论(0)    收藏  举报