题解:学而思编程 区间外最大公约数
【题目来源】
学而思编程:区间外最大公约数
【题目描述】
给定一个长度为 \(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
【核心思想】
-
问题分析:给定长度为 \(n\) 的数列 \(a_1, a_2, \ldots, a_n\),\(q\) 次询问区间 \([l, r]\) 以外的所有元素的最大公约数。这是一个前缀 GCD + 后缀 GCD问题,关键在于利用 GCD 的结合律,将区间外元素的 GCD 分解为前缀和后缀两部分。
-
算法选择:
- 前缀 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])\)
- 前缀 GCD 数组:
-
关键步骤:
- 初始化:读取 \(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
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log V + q \log V)\),\(V\) 为值域,预处理 \(O(n)\) 次 GCD,每次询问 \(O(1)\)
- 空间复杂度:\(O(n)\),前缀 GCD 数组
lgcd[0..n]和后缀 GCD 数组rgcd[1..n+1]
-
前缀后缀分解的核心思想:
- GCD 结合律:\(gcd(A \cup B) = gcd(gcd(A), gcd(B))\),区间外元素天然分为前缀 \([1, l-1]\) 和后缀 \([r+1, n]\) 两部分,两部分无交集
- 离线预处理:前缀和后缀 GCD 与询问无关,可预先计算,使每次查询降为 \(O(1)\)
- 边界处理:
lgcd[0] = 0和rgcd[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
浙公网安备 33010602011771号