# 答案
“`C++
#include
using namespace std;
int main() {
// 根据题目数据量推测,a,b,c 不会非常大,500 以内暴力枚举即可
// 如果需要更大范围,可以根据数学推导算出大概范围
int ans[3]{ INT_MAX, INT_MAX, INT_MAX };
for (long long a = 1; a <= 500; a++) {
for (long long b = 1; b <= 500; b++) {
for (long long c = 1; c <= 500; c++) {
long long n = a + b + c;
// 计算总组合数 C(N, 2)
long long total = n * (n - 1) / 2;
// 题目给出的概率:
// P(1,2) = (a * b) / total = 517 / 2091
// P(2,3) = (b * c) / total = 2632 / 10455
// P(1,3) = (a * c) / total = 308 / 2091
// 使用交叉相乘避免浮点数误差: (a * b) * 2091 == total * 517
if (a * b * 2091 != total * 517) continue;
if (b * c * 10455 != total * 2632) continue;
if (a * c * 2091 != total * 308) continue;
// 找到答案,按题目要求的格式输出
cout << a << "," << b << "," << c << endl;
if (a + b + c < ans[0] + ans[1] + ans[2]) {
ans[0] = a;
ans[1] = b;
ans[2] = c;
}
return 0;
}
}
}
cout << ans[0] << "," << ans[1] << "," << ans[2] << endl;
return 0;
}
```
# 错误
```C++
#include
#include
#include
#include
using namespace std;
int main() {
int ans[3]{ INT_MAX, INT_MAX, INT_MAX };
int n = 1e4 + 3;
vector
cnt_sub[0] = 1;
for (int i = 1; i < n; i++)
{
cnt_sub[i] = cnt_sub[i - 1] * i;
}
for (int i = 1; i <= n; i++)
{
for (int j = i; j <= n; j++)
{
long long ab = i * j;
for (int k = 1; k <= n; k++)
{
long long sum = (i + j + k) * (i + j + k - 1) / 2;
if (ab / sum == 517.00 / 2091 && ab / sum == 2632.00 / 10455 && ab / sum == 308.00 / 2091)
{
if (i + j + k < ans[0] + ans[1] + ans[2])
{
ans[0] = i;
ans[1] = j;
ans[2] = k;
cout << i << " " << j << " " << k << endl;
}
}
}
}
}
cout << "end ans: " << ans[0] << " " << ans[1] << " " << ans[2] << endl;
return 0;
}
```
### 核心差异总结表
| **特性** | **正确代码 (The Solution)** | **错误代码 (The Mistake)** |
| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| **数值比较** | **交叉相乘 (Cross-multiplication)** 将除法转换为乘法,完全避免误差。 | **浮点数/整数除法** 直接用 `==` 比较浮点数,且存在整数截断风险。 |
| **逻辑判断** | **独立校验** 分别验证 $P_{1,2}, P_{2,3}, P_{1,3}$ 是否符合条件。 | **逻辑谬误** 试图让一个数值 `ab/sum` 同时等于三个不同的概率值。 |
| **时间复杂度** | **合理枚举** $500^3$ 约为 $10^8$,在C++ 1秒限时内勉强可过或很快找到解退出。 | **超时 (TLE)** $10000^3$ 是天文数字,程序永远跑不完。 |
| **数据范围** | **正常** 使用 `long long` 处理乘积。 | **溢出 (Overflow)** 试图计算 $10000!$ (阶乘),远远超出 `long long` 范围。 |
------
### 详细笔记
#### 1. 避免浮点数误差 (精度陷阱)
这是最关键的技巧。在计算机中,`1.0 / 3.0 * 3.0` 并不一定严格等于 `1.0`。
- **错误做法:** `if (a / total == x / y)`
- 如果是整数变量,`a / total` 会直接截断取整(变成0)。
- 即使强转 double,`==` 比较也可能因为 $0.00000001$ 的误差导致判断失效。
- **正确做法 (黄金法则):** **交叉相乘**。
- 将 $\frac{A}{B} = \frac{X}{Y}$ 变形为 $A \times Y = X \times B$。
- 代码体现:`if (a * b * 2091 != total * 517) continue;` (使用整型乘法,精确无误)。
#### 2. 逻辑条件的正确书写
- **错误代码的问题:**
```C++
// 错误逻辑:这行代码要求 (i*j)/sum 同时等于三个不同的数,这是不可能的
if (val == P1 && val == P2 && val == P3)
```
它实际上是在找一个组合,使得 $a \times b$ 等于 $b \times c$ 且等于 $a \times c$,且概率还要符合题目给出的三个不同分数,逻辑矛盾。
- 正确逻辑:
题目给出的是三组独立的约束条件,应该分别检查。
```C++
// 检查 P(1,2)
if (不满足条件1) continue;
// 检查 P(2,3)
if (不满足条件2) continue;
// 检查 P(1,3)
if (不满足条件3) continue;
```
#### 3. 复杂度和枚举范围
- **估算:** 现代评测机通常 1 秒能处理 $10^8$ 次左右的基本运算。
- **500的循环:** $500 \times 500 \times 500 = 1.25 \times 10^8$。有点极限,但因为找到答案就 `return 0`,实际运行极快。
- **10000的循环:** $10000^3 = 10^{12}$。这需要运行好几个小时甚至几天。
- **经验:** 如果是 $O(N^3)$ 的暴力解法,N 通常不能超过 500。
#### 4. 阶乘溢出常识
- **错误代码片段:**
```C++
// 这里的 cnt_sub 会在 i=21 左右就溢出负数,i=10000 更是完全错误
cnt_sub[i] = cnt_sub[i - 1] * i;
```
- **常识:** `long long` (64位整数) 最大只能存 $20!$ (20的阶乘)。任何试图计算更大阶乘的行为在非高精度算法中都是错误的。且本题解法根本不需要阶乘,这属于**无效计算**。
------
### 建议的复习策略
下次遇到类似题目(已知概率求原数量),按以下步骤思考:
1. **确定范围:** 先估算数据规模,确定能否暴力枚举。
2. **公式推导:** 写出 $\frac{部分}{总数} = \frac{分子}{分母}$。
3. **整数化:** 移项变成乘法形式,避开除法和小数。
4. **剪枝:** 这是一个 $N^3$ 的循环,能否通过数学关系(例如已知 $a, b$ 和 $P_{1,2}, P_{2,3}$ 推导 $c$)将循环降维到 $N^2$ 甚至 $N$?(本题其实可以直接通过数学推导算出大致比例,不需要三层死循环)。