CF1154G 题解(gcd/lcm的枚举优化)
Contents
题目链接
https://codeforces.com/contest/1154/problem/G
题意
给定 $n$ 个正整数 $a_1,a_2,…,a_n$,求 $i \neq j$ 使得 $\text{lcm}(a_i, a_j)$ 最小?
其中 $2 \leq n \leq 10^6, 1 \leq a_i \leq 10^7$
题解
一般和 $gcd, lcm$ 相关的题,一般就 $2$ 种trick:
- 质因子分解
- 枚举 $gcd$ 的值
这道题是 枚举 $gcd$ 的值。因为 $lcm(a_i,a_j) = \frac{a_ia_j}{\gcd(a_i,a_j)}$,所以我们枚举一下 $gcd(a_i,a_j)$ 的值即可。
设 $g$ 为可能的 $gcd$ 的值,从 $1$ 枚举到 $10^7$,对于每一个 $g$,只要找出 最小的两个 $a_i,a_j$ 使得 $g|a_i, g|a_j$ 即可。
时间复杂度:$T(n) = \frac{10^7}{1} + \frac{10^7}{2} + … + \frac{10^7}{10^7} = 10^7(1+\frac{1}{2} + … \frac{1}{10^7}) = O(10^7 \log(10^7))$
证明正确性:
无论最终答案是什么,$\gcd(a_i,a_j)$ 必然会被枚举到。所以不会漏解。
如果我们枚举到的 $g$ 不一定是真的 $gcd$ 呢?比如 $g = 2$,然后 $a_i$ 中最小的两个倍数为 $4, 8$?
答:我们总会枚举到真正的 $gcd$,如果 $g$ 不是真实的 $gcd$,它只会比真实的 $gcd$ 更小,所以获得的 $lcm$ 更大,所以不影响答案的正确性。
另:枚举 $gcd$ 的trick之前在 Atcoder-ABC-162E 也出现过。
代码
using namespace std;
#include <bits/stdc++.h>
#define ll long long
const int maxn = 1e6+5;
const int maxm = 1e7+5;
int arr[maxn];
int vis[maxm]; // 不要用 unordered_set, 会TLE
int n;
ll ans = 1e18;
int ai,aj;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d",&arr[i]);
if (vis[arr[i]] && arr[i] < ans) ans = arr[i], ai = vis[arr[i]], aj = i;
vis[arr[i]] = i;
}
for (int g = 1; g <= 1e7; g++) {
int cur = 0;
if ((g<<1) > ans) break; // 优化时间
for (int j = g; j <= 1e7; j += g) {
if (vis[j]) {
if (cur) {
int d = j / g;
ll r = 1LL * cur * d;
if (r < ans) {
ai = vis[cur];
aj = vis[j];
ans = r;
}
break;
} else cur = j;
}
}
}
if (ai > aj) swap(ai, aj);
printf("%d %d\n", ai,aj);
}