暴力–精度问题

# 答案

“`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(n + 1, 0);
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$?(本题其实可以直接通过数学推导算出大致比例,不需要三层死循环)。

发表评论