一、提问词
你是一个具有10年以培训经验的信息奥赛教练,现请你撰写NOIP2009提高组T1潜伏者题解,至少包括以下几个部分:
'''
1、题目描述
2、解题思路
3、实现代码
4、代码说明
'''
二、IMA+DeepSeek回复
1. 题目描述
问题背景 1
Hanks博士提出一个关于公约数和公倍数的逆问题:已知正整数 a0,a1,b0,b1求满足以下两个条件的正整数 xx 的个数:
- 1.公约数条件:gcd(x,a0)=a1
- 2.公倍数条件:lcm(x,b0)=b1
输入输出格式
- 输入:第一行为 nn(测试组数),随后 nn 行每行给出 a0,a1,b0,b1
- 输出:每组输出满足条件的 x 的数量。
数据范围
- 1≤n≤2000,所有数均为 ≤2×10^9 的正整数。
2. 解题思路
关键性质分析
- 1.公约数约束:gcd(x,a0)=a1 ⟹ x 必须是 a1的倍数,且 x 和 a0/a1 互质。
- 2.公倍数约束:lcm(x,b0)=b1 ⟹ b1 必是 x 的倍数,x 需满足 x∣b1
数学推导与优化
- 暴力枚举 xx 会超时,需缩小枚举范围:
- x 必须是 b1 的约数(通过枚举 b1 加速)。
- 对每个约数 x 验证两个条件是否成立。
算法步骤
- 枚举 b1 的所有因子 x。
- 对每个 \( x \ ):
- 检查 gcd(x,a0)=a1 且 lcm(x,b0)=b1。
- 统计所有合法的 xx 的数量。
3. 实现代码
#include <bits/stdc++.h>
using namespace std;
// 求最大公约数[1](@ref)
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
// 求最小公倍数(需转long long防溢出)[2](@ref)
int lcm(int a, int b) {
return (long long)a * b / gcd(a, b);
}
// 处理单个测试用例
void solve(int a0, int a1, int b0, int b1) {
int cnt = 0;
for (int x = 1; x * x <= b1; x++) {
if (b1 % x != 0) continue;
// 检查当前因子x是否合法
if (gcd(x, a0) == a1 && lcm(x, b0) == b1) cnt++;
// 检查对应的另一因子b1/x是否合法
int y = b1 / x;
if (x != y) {
if (gcd(y, a0) == a1 && lcm(y, b0) == b1) cnt++;
}
}
printf("%d\n", cnt);
}
int main() {
freopen("son.in", "r", stdin);
freopen("son.out", "w", stdout);
int n, a0, a1, b0, b1;
scanf("%d", &n);
while (n--) {
scanf("%d%d%d%d", &a0, &a1, &b0, &b1);
solve(a0, a1, b0, b1);
}
return 0;
}
4. 代码说明
核心函数设计
gcd()
:递归实现辗转相除法,计算最大公约数。lcm()
:基于公式,用
long long
转换防止乘法溢出。
主要流程逻辑
- 因子枚举:遍历 b1 范围内的所有整数,若 x 是因子,则对应的另一因子为 b1/x。 2
- 条件验证:对每个因子 x 及其对应的另一因子 y=b1/x,验证是否满足公约数和公倍数条件。
复杂度分析
- 每组测试数据的复杂度为 O(b1),满足 b1≤2×10^9时的实际效率需求。
示例测试
输入 | 输出 |
---|---|
2 | |
1 1 1 1 | 1 |
105 1 60 1 | 0 |
解释:
- 第一组:唯一解为 x=1。
- 第二组:无解,因无法同时满足两条件。
总结
本题考查数论优化能力,高效的因子枚举是解题的核心。通过挖掘题目性质,将暴力枚举的复杂度降低到 O(b1),确保算法的高效性。