CF1285F Classical? 题解

· · 题解

题意

给出 n 个数 a_1,a_2,\ldots,a_n,求

\max_{1\le i<j\le n}\mathrm{lcm}(a_i,a_j)

限制

#### 题解 令 $A=\max\{a_i\}$。 考虑加上 $\gcd(a_i,a_j)=1$ 的限制。从大到小扫集合里的数,可以发现如果扫到 $x$ 时存在 $y>x$ 且 $\gcd(x,y)=1$,则所有满足 $x<z<y$ 的数 $z$ 不会对答案有贡献了。因此用栈储存扫过的元素,扫到一个数 $x$ 时,只要栈中有与 $x$ 互素的数就弹栈,并与 $x$ 更新答案。如何快速判断是否有与 $x$ 互素的数?考虑容斥。记 $\mathrm{cnt}_i$ 为 $i$ 的倍数的个数,则与 $x$ 互素的数的个数为 $$ \sum_{d|x}\mu(d)\mathrm{cnt}_d $$ 这个过程的复杂度为 $O(\sum_{i=1}^Ad(i))=O(A\log A)$。 接下来可以枚举 $\gcd(a_i,a_j)=g$ 并对所有 $g$ 的倍数执行上述过程,时间复杂度 $O(A(\log A)^2)$。 更巧妙的做法是把每个 $a_i$ 的所有因数加入集合。假设原始的答案由 $x$ 和 $y$ 两个数得到,则一定存在两个数 $a\mid x,b\mid y$ 满足 $\mathrm{lcm}(a,b)=\mathrm{lcm}(x,y)$ 且 $\gcd(a,b)=1$,因此只需要执行一次上述过程。时间复杂度:$O(A\log A)$。 #### 代码 ```cpp #include <iostream> #include <vector> #include <algorithm> int n; std::vector<bool> a; std::vector<int> stk, cnt, mu; std::vector<std::vector<int>> divisors; long long ans; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cin >> n; for (int i = 0; i < n; ++i) { int x; std::cin >> x; if (int(a.size()) <= x) a.resize(x + 1); a[x] = true; ans = std::max(ans, 1LL * x); } int m = a.size() - 1; mu.resize(m + 1); mu[1] = 1; for (int i = 1; i <= m; ++i) for (int j = 2 * i; j <= m; j += i) mu[j] -= mu[i]; divisors.resize(m + 1); for (int i = 1; i <= m; ++i) { for (int j = i; j <= m; j += i) { if (mu[i] != 0) divisors[j].push_back(i); a[i] = a[i] || a[j]; } } cnt.resize(m + 1); for (int i = m; i >= 1; --i) { if (!a[i]) continue; int num = 0; for (int j : divisors[i]) num += mu[j] * cnt[j]; while (num > 0) { int lst = num; for (int j : divisors[stk.back()]) { --cnt[j]; if (i % j == 0) num -= mu[j]; } if (lst != num) ans = std::max(ans, 1LL * i * stk.back()); stk.pop_back(); } for (int j : divisors[i]) ++cnt[j]; stk.push_back(i); } std::cout << ans << "\n"; return 0; } ```