1. 题目背景与需求解析
"和差还原"是上海计算机学会2026年3月月赛C++丙组的一道基础编程题,主要考察选手对基本数学运算和逻辑推理的掌握能力。这类题目在编程竞赛中非常典型,通常作为第一题出现,旨在检验参赛者的基础编码能力和问题转化技巧。
1.1 题目描述还原
根据常见的竞赛题型推断,题目大致描述如下:
给定两个正整数a和b,已知:
- a + b = S(和)
- a - b = D(差,假设a ≥ b)
要求编写程序,根据输入的S和D值,计算出原始的a和b的值。如果输入的S和D无法构成有效的正整数解(如差大于和、和为负数等情况),则需要输出特定的错误提示。
1.2 数学原理分析
这道题的核心在于二元一次方程组的求解。我们可以通过以下数学推导得到解决方案:
-
将两个等式相加:
(a + b) + (a - b) = S + D → 2a = S + D → a = (S + D)/2 -
将两个等式相减:
(a + b) - (a - b) = S - D → 2b = S - D → b = (S - D)/2
注意:在实际编程实现时,必须考虑计算结果是否为整数以及是否为正数这两个关键条件。
2. 解题思路与算法设计
2.1 基本解法
最直接的解法就是按照数学推导公式实现:
- 接收输入的两个整数S和D
- 计算a = (S + D)/2
- 计算b = (S - D)/2
- 验证a和b是否都为正整数
- 根据验证结果输出答案或错误提示
2.2 边界条件分析
在实现过程中需要考虑以下特殊情况:
- S和D的奇偶性必须一致(因为S+D和S-D都必须是偶数)
- S必须大于D(否则b会是负数)
- S和D都必须为正数
- 计算结果必须为整数(不能有小数部分)
2.3 算法优化思考
虽然这道题的计算量很小,不需要复杂优化,但我们可以考虑:
- 先进行输入验证,提前排除不可能的情况
- 使用位运算判断奇偶性(效率略高于模运算)
- 合并部分判断条件,减少不必要的计算
3. 代码实现与详细注释
3.1 C++基础实现
cpp复制#include <iostream>
using namespace std;
int main() {
int S, D;
cin >> S >> D;
// 检查基本条件
if (S < D || (S + D) % 2 != 0 || (S - D) % 2 != 0) {
cout << "invalid" << endl;
return 0;
}
int a = (S + D) / 2;
int b = (S - D) / 2;
// 二次验证结果是否为正数
if (a > 0 && b > 0) {
cout << a << " " << b << endl;
} else {
cout << "invalid" << endl;
}
return 0;
}
3.2 代码解析与关键点
- 输入处理:使用标准输入流读取两个整数
- 条件检查:
- S必须大于等于D
- S+D和S-D都必须是偶数
- 计算过程:直接套用推导公式
- 结果验证:确保a和b都是正整数
- 输出处理:根据条件输出结果或错误提示
3.3 代码优化版本
cpp复制#include <iostream>
using namespace std;
int main() {
int S, D;
cin >> S >> D;
// 合并条件判断
if (S >= D && !((S ^ D) & 1)) { // 使用位运算判断奇偶性一致
int a = (S + D) >> 1; // 使用右移代替除法
int b = (S - D) >> 1;
if (a > 0 && b > 0) {
cout << a << " " << b << endl;
return 0;
}
}
cout << "invalid" << endl;
return 0;
}
4. 测试用例设计与验证
4.1 常规测试用例
| 输入(S D) | 预期输出 | 说明 |
|---|---|---|
| 10 6 | 8 2 | 正常情况 |
| 15 5 | 10 5 | 正常情况 |
| 100 20 | 60 40 | 大数测试 |
4.2 边界测试用例
| 输入(S D) | 预期输出 | 说明 |
|---|---|---|
| 1 1 | invalid | 差等于和 |
| 5 6 | invalid | 差大于和 |
| 7 3 | invalid | 和与差奇偶性不同 |
| 0 0 | invalid | 零输入 |
| -2 -4 | invalid | 负数输入 |
4.3 特殊测试用例
| 输入(S D) | 预期输出 | 说明 |
|---|---|---|
| 2147483647 1 | 1073741824 1073741823 | 最大整数测试 |
| 2 0 | 1 1 | 差为零 |
| 3 1 | 2 1 | 奇数情况 |
5. 常见错误与调试技巧
5.1 典型错误分析
-
未验证整数条件:
cpp复制// 错误示例:没有检查(S+D)和(S-D)是否能被2整除 int a = (S + D) / 2; // 当S+D为奇数时,a会被截断 -
条件判断顺序错误:
cpp复制// 错误示例:先计算再判断,可能导致整数溢出 int a = (S + D) / 2; int b = (S - D) / 2; if (a > 0 && b > 0) {...} // 如果S-D为负数,b已经错误了 -
忽略零和负数情况:
cpp复制// 错误示例:没有处理非正整数输入 if (S > D) {...} // 当S或D为0或负数时可能出错
5.2 调试技巧
-
打印中间变量:
在关键计算步骤后添加调试输出,确保中间结果符合预期。 -
边界值测试:
特别关注S=D、S=D+1、S=0、D=0等情况。 -
防御性编程:
使用断言(assert)验证关键条件,如:cpp复制assert((S + D) % 2 == 0);
6. 算法扩展与变种思考
6.1 浮点数版本
如果题目允许a和b为浮点数,解法会有所不同:
cpp复制#include <iostream>
#include <cmath>
using namespace std;
int main() {
double S, D;
cin >> S >> D;
double a = (S + D) / 2.0;
double b = (S - D) / 2.0;
if (isnan(a) || isnan(b) || a < 0 || b < 0) {
cout << "invalid" << endl;
} else {
cout << a << " " << b << endl;
}
return 0;
}
6.2 多组数据输入
竞赛中常见需要处理多组测试数据的情况:
cpp复制#include <iostream>
using namespace std;
void solve() {
int S, D;
while (cin >> S >> D) {
if (S >= D && !((S ^ D) & 1)) {
int a = (S + D) >> 1;
int b = (S - D) >> 1;
if (a > 0 && b > 0) {
cout << a << " " << b << endl;
continue;
}
}
cout << "invalid" << endl;
}
}
int main() {
solve();
return 0;
}
6.3 其他变种思路
- 三数和差问题:已知a+b+c、a+b-c、a-b+c等组合,求a、b、c
- 模运算版本:在模数条件下求解和差还原
- 多解情况:当存在多个解时的处理方式
7. 竞赛技巧与时间优化
7.1 快速解题策略
- 优先处理边界条件:先写输入验证部分,可以避免后续复杂计算
- 使用位运算:如用
(S ^ D) & 1代替(S + D) % 2 != 0的判断 - 提前返回:一旦发现不满足条件立即返回,减少嵌套层次
7.2 代码模板准备
对于这类基础数学题,可以准备常用模板:
cpp复制bool solveSumDiff(int S, int D, int &a, int &b) {
if (S < D || (S ^ D) & 1) return false;
a = (S + D) >> 1;
b = (S - D) >> 1;
return a > 0 && b > 0;
}
7.3 常见竞赛陷阱
- 变量范围:没有考虑大整数情况,导致溢出
- 输出格式:多输出或少输出空格、换行
- 时间限制:虽然本题简单,但复杂变种可能需要注意算法效率
在实际竞赛中,这类基础题目通常需要在5-10分钟内完成,包括读题、编码和测试。因此熟练掌握基本数学问题的编程实现非常重要。建议平时多练习类似的数学编程题,建立快速解题的思维模式。