CF1285F Classical?
首先看到 \(lcm\) 就可能想到枚举 \(\gcd\),只考虑 \(\gcd\) 倍数的数。现在我们的任务是,从一堆数中找到 \(\gcd\) 恰好 为 \(g\) 的 \(x,y\),最大化 \(x \times y\)。所有合法数都除以 \(g\) 以后变成从一堆数中找到互质的 \(x,y\),最大化 \(x \times y\)。
这样有什么优势呢?显然,\(x \times y\) 比 \(lcm(x,y)\) 有更多的好性质可以利用。我们有一个基于 \(x \times y\) 的贪心做法:从大到小枚举 \(x\),并在之前找到与 \(x\) 互质的最大的 \(y\),用 \(x \times y\) 更新答案,然后小于等于 \(y\) 的数就可以扔掉了(以后的 \(x\) 会越来越小,不扔也更新不了答案)。这样我们就有了一种类似单调栈的做法,就可以 \(O(nlnn)\) 通过此题了
等等,如何找到与 \(x\) 互质的最大的 \(y\)?我们或许需要知道栈里面有多少个数与 \(x\) 互质,设其为 \(g(x)\)。然后发现它有点像数论推式子:(设 \(A\) 为 \(max(a_i)\),数 \(i\) 的出现次数为 \(c_i\))
\[\begin{aligned}
g(x) &= \sum_{i = 1}^A [(i,x)=1]c_i \\
&= \sum_{i=1}^Ac_i\sum_{d|i,d|x}\mu(d) \\
&= \sum_{d|x} \mu(d)\sum_{i=1}^A[d|i]c_i \\
&= \sum_{d|x} \mu(d)F(d)
\end{aligned}\]
其中 \(F(x)\) 可以在每个元素入栈时算。这样,就可以在 \(O(logn)\) 左右的时间内算出栈内与当前 \(x\) 互质的数的个数。
总复杂度:\(O(nlog^2n)\)
关键代码:
int mucnt[N];
inline int Count(int x) {
int res = 0;
for (register uint j = 0; j < vec[x].size(); ++j) {
int to = vec[x][j];
res += mu[to] * mucnt[to];
}
return res;
}
inline void calc(int bs) {
stop = 0;
for (register int i = htot; i; --i) {
int ct = Count(h[i]);
while (ct) {
int tmp = stk[stop--];
int g = get_gcd(tmp, h[i]);
if (g == 1) {
MAX(ans, 1ll * tmp * h[i] * bs);
--ct;
}
for (register uint j = 0; j < vec[tmp].size(); ++j)
--mucnt[vec[tmp][j]];
}
stk[++stop] = h[i];
for (register uint j = 0; j < vec[h[i]].size(); ++j) {
int to = vec[h[i]][j];
++mucnt[to];
}
}
for (register int i = htot; i; --i)
for (register uint j = 0; j < vec[h[i]].size(); ++j)
mucnt[vec[h[i]][j]] = 0;
}
int main() {
for (register int i = 1; i <= up; ++i)
for (register int j = i; j <= up; j += i)
vec[j].push_back(i);
for (register int i = 1; i <= up; ++i) {
htot = 0;
for (register int j = i, k = 1; j <= up; j += i, ++k) if (bin[j]) h[++htot] = k;
calc(i);
}
printf("%lld\n", ans);
return 0;
}