题目

思路
一、核心逻辑(先立框架,再填细节)
核心:把 "解复杂方程" 转化为 "找因数对"
- 数学变形:将二元嵌套方程转化为 "两数相乘等于定值",实现从 "难" 到 "易" 的突破;
- 高效枚举:利用因数对称性缩小枚举范围,避免超时;
- 约束过滤:筛选符合 "x/y 非负" 的有效解,按要求整理输出。
二、逐环节拆解(带思考 + 实操 + 细节)
环节 1:为什么必须做数学变形?(吃透原理)
直接求解的痛点
原方程 xy + ax + b*y = c 中,x 和 y 相互嵌套:
- 若直接枚举 x,需通过 y = (c - a*x)/(x + b) 计算 y,需同时满足:
- 分母 x + b ≠ 0(恒成立,因 x≥0、b 为正整数);
- 分子 c - a*x ≥ 0;
- 分子能被分母整除;
- 当 c 取 1e9 时,枚举 x 的范围达 1e9 次,必然超时。
变形过程(记死配凑技巧)
- 原方程:xy + ax + b*y = c
- 两边加 ab(配凑因式分解的关键,目的是分组提公因子): x y + ax + b y + ab = c + ab
- 分组提取公因子:x (y + a) + b (y + a) = c + ab → (x + b)(y + a) = c + ab
变量替换与约束转化
- 令 A = x + b,B = y + a,方程简化为:AB = target(其中 target = c + ab);
- 由 x≥0、y≥0 推导约束条件:
- x = A - b ≥ 0 → A ≥ b(等号不可丢!A=b 时 x=0,是合法解);
- y = B - a ≥ 0 → B ≥ a(同理,B=a 时 y=0,是合法解)。
环节 2:如何高效找因数对 (A,B)?(落地操作)
因数的核心性质(效率关键)
若 A 是 target 的因数,则 B = target/A 必为 target 的因数,且 A 和 B 中必有一个≤√target。
- 示例:target=20,√20≈4,枚举 A=1、2、4 即可覆盖所有因数对:(1,20)、(2,10)、(4,5),枚举次数从 20 次降至 3 次。
实操步骤
- 枚举范围:A 从 1 到√target(用 A*A ≤ target 替代 A ≤ √target,避免浮点精度误差,如√100 可能算出 9.99999);
- 因数判断:若 target % A == 0,则 A 是因数,计算对应 B=target/A;
- 有效解检查:
- 检查 (A,B):若 A ≥ b 且 B ≥ a,则还原解 x = A - b、y = B - a,存入结果集;
- 检查 (B,A):若 A≠B(避免平方数重复存解,如 target=16,A=4、B=4),且 B ≥ b 且 A ≥ a,则还原另一组解,存入结果集。
环节 3:避坑细节(关键保障,必看)
| 坑点 | 典型表现 | 解决办法 |
|---|---|---|
| 数据溢出 | a=1e5、b=1e5 时,a*b=1e10 超 int 范围,target 计算错误 | 用 long long 存储 target、A、B;计算时先强制转换:(long long) a * b |
| 约束搞反 | 误将条件写为 A ≥ a、B ≥ b | 死记:A 对应 b(x=A-b),B 对应 a(y=B-a) |
| 漏等号 | 条件写为 A > b、B > a,漏掉 x=0 或 y=0 的解 | 严格写 A ≥ b、B ≥ a |
| 解重复 | target 为平方数时(如 16),同一组解存两次 | 仅当 A≠B 时,才检查 (B,A) |
| 解无序 | 枚举结果 x 乱序,不符合输出要求 | 用数组存解后,按 x 升序排序 |
三、核心记忆点(一眼回忆全流程)
- 变形公式:方程 + ab → (x+b)(y+a)=c+ab;
- 枚举边界:A 遍历到√target,用 A*A≤target 避浮点误差;
- 约束条件:A≥b、B≥a,等号不可丢;
- 避坑关键:long long 防溢出,A≠B 去重,排序保顺序。
四、同类题迁移思路(遇到新题怎么想)
- 观察方程:能否通过配凑、因式分解转化为 "简单乘积形式";
- 约束转化:将原变量的限制(非负、范围)转化为新变量的约束;
- 效率优化:利用数学性质(因数对称、质数筛等)缩小枚举范围;
- 边界处理:解决溢出、重复、等号等细节问题,按题目要求整理输出。
代码
cpp
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
typedef struct {
int x;
int y;
} Solution;
// 比较函数,用于按x从小到大排序(其实枚举时已经有序,可省略)
int cmp(const void *a, const void *b) {
return ((Solution*)a)->x - ((Solution*)b)->x;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
long long target = (long long)c + (long long)a * b; // 防止溢出
Solution sols[10000]; // 存储解,足够大
int cnt = 0;
// 枚举所有因数A
for (long long A = 1; A * A <= target; A++) {
if (target % A == 0) {
long long B = target / A;
// 检查A和B是否满足条件
if (A >= b && B >= a) {
sols[cnt].x = A - b;
sols[cnt].y = B - a;
cnt++;
}
// 处理A≠B的情况,避免重复
if (A != B) {
if (B >= b && A >= a) {
sols[cnt].x = B - b;
sols[cnt].y = A - a;
cnt++;
}
}
}
}
// 按x排序(可选,因为枚举A是从小到大的)
qsort(sols, cnt, sizeof(Solution), cmp);
// 输出结果
printf("%d\n", cnt);
for (int i = 0; i < cnt; i++) {
printf("%d %d\n", sols[i].x, sols[i].y);
}
}
return 0;
}