🚀【算法日记 14】数论入门神题:最大公约数与最小公倍数的"乘积守恒定律"
📍 题目背景:洛谷 P1029 [NOIP2001 普及组]
这是一道来自 NOIP 2001 年的经典普及组题目。题目要求给定两个正整数 x0x_0x0 (最大公约数) 和 y0y_0y0 (最小公倍数),求满足条件的整数对 (P,Q)(P, Q)(P,Q) 的个数。
【输入格式】
一行两个正整数 x0,y0x_0, y_0x0,y0。
【输出格式】
一行一个数,表示求出满足条件的 P,QP, QP,Q 的个数。
【输入输出样例】
输入:
text
3 60
输出:
text
4
【说明/提示】
P,QP, QP,Q 有 4 种:
3, 60
15, 12
12, 15
60, 3
对于 100% 的数据,2≤x0,y0≤1052 \le x_0, y_0 \le 10^52≤x0,y0≤105。
✨ 核心数学逻辑:乘积守恒定律
在处理这类题目时,最核心的物理规律是:
任意两个正整数 PPP 和 QQQ 的乘积,等于它们的最大公约数(GCD)与最小公倍数(LCM)的乘积。
P×Q=gcd(P,Q)×lcm(P,Q)P \times Q = \text{gcd}(P, Q) \times \text{lcm}(P, Q)P×Q=gcd(P,Q)×lcm(P,Q)
🎯 降维打击思路:
- 已知条件 :我们已知 x0=gcd(P,Q)x_0 = \text{gcd}(P, Q)x0=gcd(P,Q) 和 y0=lcm(P,Q)y_0 = \text{lcm}(P, Q)y0=lcm(P,Q)。
- 转换方程 :P×Q=x0×y0P \times Q = x_0 \times y_0P×Q=x0×y0。
- 减少变量 :我们不需要枚举 PPP 和 QQQ。只需要枚举 PPP,那么 QQQ 就可以直接通过 x0×y0P\frac{x_0 \times y_0}{P}Px0×y0 算出来!
- 确定范围 :
- PPP 必须是 x0x_0x0 的倍数,所以起始值是 x0x_0x0,步长是 x0x_0x0。
- PPP 最大不会超过 y0y_0y0。
💻 最终满分 AC 代码(已排雷)
这段代码解决了初学者最容易踩的两个坑:整型溢出 和取模判断。
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
// 🚀 竞赛极速读写引擎
ios::sync_with_stdio(false);
cin.tie(0);
long long x0, y0;
if (!(cin >> x0 >> y0)) return 0;
// 💣 避坑指南 1:y0 必须能被 x0 整除,否则无解
if (y0 % x0 != 0) {
cout << 0 << "\n";
return 0;
}
long long count = 0;
// 💣 避坑指南 2:提前存储乘积,防止在循环中重复计算,且必须用 long long
long long prod = x0 * y0;
// 🚀 性能优化:步长设为 x0,极大减少循环次数
for (long long p = x0; p <= y0; p += x0) {
// 💣 避坑指南 3:必须使用 % 判断是否能整除,不能用 /
if (prod % p == 0) {
long long q = prod / p;
// 🚀 核心判定:计算出的 P 和 Q 的最大公约数必须等于 x0
if (__gcd(p, q) == x0) {
count++;
}
}
}
cout << count << "\n";
return 0;
}
💣 首席质检员的避坑总结
long long是保命符 :题目范围虽然是 10510^5105,但 x0×y0x_0 \times y_0x0×y0 会达到 101010^{10}1010,远超int的 21 亿上限。不写long long必炸!__gcd(a, b)核武器 :C++<algorithm>或<numeric>库自带的这个函数效率极高,不需要自己手写辗转相除法。- 整除判断的低级错误 :记住,判断"能否整除"用的是
a % b == 0,而不是a / b == 0。这是新手最容易在键盘上滑手的地方。 - 对称性优化(进阶) :如果你想更进一步,可以只枚举到 x0×y0\sqrt{x_0 \times y_0}x0×y0 ,然后每次
count += 2。这能把时间复杂度从 O(N)O(N)O(N) 降到 O(N)O(\sqrt{N})O(N )。
🏆 结语
这道题教会了我们:不要盲目暴力,先列数学方程。 在算法竞赛中,数学推导永远是写代码之前最高效的"外挂"。