题目来自 AtCoder 上的 GCD on Blackboard。
题目
黑板上写着 个整数 。
你将选择其中一个数并将其替换为另一个 到 的整数。这个数可以与替换之前的数相同。
替换后,黑板上所有整数的最大可能的 GCD 是多少?
注:GCD 为最大公约数,表示能整除所有 的最大数。
输入
第一行包含一个整数 ()。
第二行包含 个整数,()。
输出
输出一个整数,表示替换一个数后所有数的最大可能的 GCD。
例子
例 1
3
7 6 8
2
解释:
将 替换为 ,。这是我们能得到的最大 GCD 值。
例 2
3
12 15 18
6
解释:
将 替换为 ,。这是我们能得到的最大 GCD 值。
例 3
2
10 10
10
解释:
将一个 替换为 (也就是说没有替换),。这是我们能得到的最大 GCD 值。
题目要求
输入:,
输出:最大化
条件:可以替换一个
解题思路
关键 1
假设我们要将 替换成 ,那么替换后的最大 GCD 只能是除了 以外的数的 GCD,即
为什么?
首先,我们选择的 一定要是其他数的公约数(否则最后结果不就变成 1 了嘛)。其次, 要尽可能的大,那不就是其他数的最大公约数(GCD)了。
换句话说,虽说题目是写替换一个数,但实则和删除一个数没有差别。这一点非常关键。
关键 2
现在我们有思路了,遍历数组,尝试删除一个数,看看剩余数的 GCD 最大能是多少。
这个数据量(),两层 for 循环,铁定超时。
我们需要优化一下,使用前/后缀和预先计算 和 。
定义:
对于每个数 ,我们能直接求 。
时间复杂度:
空间复杂度:
代码
C++:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
int a[n];
for (int i = 0; i < n; i++)
cin >> a[i];
int pre[n], rpre[n];
pre[0] = a[0];
for (int i = 1; i < n; i++)
pre[i] = __gcd(pre[i - 1], a[i]);
rpre[n - 1] = a[n - 1];
for (int i = n - 2; i >= 0; i--)
rpre[i] = __gcd(rpre[i + 1], a[i]);
int ans = -1;
for (int i = 0; i < n; i++) {
int gcd_oth; // 除了 a[i] 以外的所有数的 GCD
if (i == 0) gcd_oth = rpre[i + 1];
else if (i == n - 1) gcd_oth = pre[i - 1];
else gcd_oth = __gcd(pre[i - 1], rpre[i + 1]);
ans = max(ans, gcd_oth);
}
cout << ans;
}
Python:
from math import gcd
n = int(input())
a = list(map(int, input().split()))
pre = [a[0]]
for i in range(1, n):
pre.append(gcd(pre[i - 1], a[i]))
rpre = [a[n - 1]]
for i in range(n - 2, -1, -1):
rpre.insert(0, gcd(rpre[0], a[i]))
ans = -1
for i in range(0, n):
# 除了 a[i] 以外的所有数的 GCD
if i == 0:
gcd_oth = rpre[i + 1]
elif i == n - 1:
gcd_oth = pre[i - 1]
else:
gcd_oth = gcd(pre[i - 1], rpre[i + 1])
ans = max(ans, gcd_oth)
print(ans)
总结
- 没辙,数论得学好;
- 前缀和不仅只能是“和”,XOR 和 GCD 操作都挺常见的,核心思想是这类型的计算都能一边遍历数组一边累计结果;
- 求 “除了第 个数以外的数的某某运算” 或许可以使用前缀和 + 后缀和。
相关知识点:数论、前/后缀和