首先看到 (lcm) 就可能想到枚举 (gcd),只考虑 (gcd) 倍数的数。现在我们的任务是,从一堆数中找到 (gcd) 恰好 为 (g) 的 (x,y),最大化 (x imes y)。所有合法数都除以 (g) 以后变成从一堆数中找到互质的 (x,y),最大化 (x imes y)。
这样有什么优势呢?显然,(x imes y) 比 (lcm(x,y)) 有更多的好性质可以利用。我们有一个基于 (x imes y) 的贪心做法:从大到小枚举 (x),并在之前找到与 (x) 互质的最大的 (y),用 (x imes y) 更新答案,然后小于等于 (y) 的数就可以扔掉了(以后的 (x) 会越来越小,不扔也更新不了答案)。这样我们就有了一种类似单调栈的做法,就可以 (O(nlnn)) 通过此题了
等等,如何找到与 (x) 互质的最大的 (y)?我们或许需要知道栈里面有多少个数与 (x) 互质,设其为 (g(x))。然后发现它有点像数论推式子:(设 (A) 为 (max(a_i)),数 (i) 的出现次数为 (c_i))
[egin{aligned}
g(x) &= sum_{i = 1}^A [(i,x)=1]c_i \
&= sum_{i=1}^Ac_isum_{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
", ans);
return 0;
}