数论
2025 北京市赛 I
给定三个正整数 a, b, k,求LCM(a + x, b + y)的最小值(LCM是指两个数的最小公倍数),其中 x 和 y 都是不大于k的非负整数
首先从比较简单的情况入手,如果 a <= b 且 a + k >= b,那么显然可以得到此时的答案是b,因为这样b至少不需要乘上某一个倍数
但当这种关系不成立的情况下,尝试得出最优解就有些困难了,因此考虑从规范化的形式去考虑
令 \(d = gcd(a, b)\) (这里的a,b代表加上x, y后的a'和b'),那么\(lcm(a, b) = \frac{a * b} {d}\),这个形式上如果要追求左侧数值的最小化,那么就是想让a * b尽可能小,而d尽可能大,但这对我们的思考仍然没有什么用处,因此进一步变形,表示为\(lcm(a, b) = d \cdot \left\lceil \frac{a}{d} \right\rceil \cdot \left\lceil \frac{b}{d} \right\rceil\)
观察现在的这几个变量,假设我们固定了d,那么要让\(\left\lceil \frac{a}{d} \right\rceil\)尽可能小,实际上就是取原始的a/d上取整,因此,如果我们能确定d,那么就可以得到答案
于是现在考虑能不能枚举d,但是一方面,a和b的数值范围都很大,有1E14,另一方面,考虑到a + k 对数值的影响也较大,我们并不适合枚举质因数
这时注意到我们对于\(\left\lceil \frac{a}{d} \right\rceil\)的取值是已经确定是基于一开始的a选择了,并且该取值范围是 \(\sqrt{a}\) 级别的,因此相较于枚举d,可以考虑枚举\(\left\lceil \frac{a}{d} \right\rceil\),并且当这个值确定后,为了使得最后的数值最小,我们一定是选择满足条件的最小的d
于是要遍历这\(O(\sqrt{a} + \sqrt{b})\)个可能的值,并计算答案,这可以使用整除分块来实现
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
using i128 = __int128;
ostream& operator<<(ostream &os, i128 x) {
if (x < 0) {
os << '-';
x = -x;
}
if (x == 0) {
os << '0';
return os;
}
string s;
while (x > 0) {
s += char(x % 10 + '0');
x /= 10;
}
reverse(s.begin(), s.end());
os << s;
return os;
}
i64 ceil(i64 a, i64 b) {
return (a + b - 1) / b;
}
void solve() {
i64 a, b, k;
cin >> a >> b >> k;
i128 ans = 1e30;
{
for (i64 d = a, r = a; r >= 1; r = d - 1) {
i64 tmp = ceil(a, r); // tmp = ceil(a, r):如果商为 r,那么除数至少是 ⌈a/r⌉
d = ceil(a, tmp); // 为了让 ⌈a/d⌉ = r,d 的最小值是 ⌈a/tmp⌉
i64 u = d * ceil(a, d);
i64 v = d * ceil(b, d);
if (u <= a + k && v <= b + k) {
ans = min(ans, (i128)u / d * v);
}
// r = d - 1,跳跃到下一个不同的商值
// 对于 ⌈a/d⌉ = r,不同的商值对应的 d 范围是连续的,于是减1进入下一个商值范围
}
}
swap(a, b);
{
for (i64 d = a, r = a; r >= 1; r = d - 1) {
i64 tmp = ceil(a, r);
d = ceil(a, tmp);
i64 u = d * ceil(a, d);
i64 v = d * ceil(b, d);
if (u <= a + k && v <= b + k) {
ans = min(ans, (i128)u / d * v);
}
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}

浙公网安备 33010602011771号