1. 项目概述:乒乓球比赛计分系统
这个东华OJ基础题要求用C++实现一个乒乓球比赛的计分系统。作为编程入门者必练的经典题目,它考察了字符串处理、状态机转换和比赛规则实现等核心编程能力。我在ACM集训时第一次遇到这个题目,当时花了整整3小时才通过所有测试用例——不是算法有多难,而是比赛规则的边界情况实在太容易遗漏。
乒乓球比赛的计分规则看似简单:先得11分者胜一局(若10:10则需领先2分),但实际处理输入输出时有诸多细节需要注意。题目通常会给出一个由'W'(Win)和'L'(Lose)组成的字符串序列,要求分别输出11分制和21分制下的比赛结果。例如输入"WWLWWWWLWWL",输出应该呈现类似:
code复制11分制:
11:5
5:11
21分制:
21:3
2. 核心算法设计
2.1 输入处理与状态跟踪
首先需要设计合理的数据结构来处理输入数据。由于输入可能包含多局比赛,且结果字符串可能很长(OJ测试用例常达10000+字符),建议直接使用字符串存储整个输入序列:
cpp复制string records;
getline(cin, records);
比赛计分需要跟踪以下状态变量:
- 当前局A/B选手得分(scoreA, scoreB)
- 已完成的局比分(results)
- 比赛是否结束(gameOver)
cpp复制struct Match {
int a = 0;
int b = 0;
vector<pair<int, int>> rounds;
};
2.2 计分规则实现
计分逻辑的核心是一个有限状态机。每次处理'W'或'L'时:
- 对应选手得分+1
- 检查是否达到胜利条件
- 若达胜利条件则保存当前局比分,重置局分数
cpp复制void processMatch(Match& match, char result, int winPoint) {
if (result == 'W') match.a++;
else match.b++;
if ((match.a >= winPoint || match.b >= winPoint) &&
abs(match.a - match.b) >= 2) {
match.rounds.emplace_back(match.a, match.b);
match.a = match.b = 0;
}
}
关键点:胜利条件判断必须同时满足"得分≥11/21"和"领先≥2分"两个条件,这是新手最容易出错的地方。
3. 完整实现方案
3.1 主处理函数框架
cpp复制void printResults(const vector<pair<int, int>>& results) {
for (const auto& [a, b] : results) {
cout << a << ":" << b << endl;
}
}
void solve(const string& records) {
Match match11, match21;
for (char ch : records) {
if (ch == 'E') break;
processMatch(match11, ch, 11);
processMatch(match21, ch, 21);
}
// 处理未完成的最后一局
if (match11.a > 0 || match11.b > 0) {
match11.rounds.emplace_back(match11.a, match11.b);
}
if (match21.a > 0 || match21.b > 0) {
match21.rounds.emplace_back(match21.a, match21.b);
}
cout << "11分制:" << endl;
printResults(match11.rounds);
cout << endl << "21分制:" << endl;
printResults(match21.rounds);
}
3.2 边界情况处理
实际提交时需要特别注意:
- 输入可能包含空格、换行等空白字符
- 'E'可能出现在任意位置表示输入结束
- 最后未完成的局也需要输出
- 空输入或只有'E'的输入应输出0:0
改进后的输入处理:
cpp复制string readInput() {
stringstream ss;
string line;
while (getline(cin, line)) {
for (char ch : line) {
if (ch == 'E') {
ss << 'E';
return ss.str();
}
if (ch == 'W' || ch == 'L') {
ss << ch;
}
}
}
return ss.str();
}
4. 性能优化与测试技巧
4.1 时间复杂度分析
该算法时间复杂度为O(n),n为输入字符串长度。对于OJ系统来说,最坏情况下(10000字符输入):
- 每个字符处理2次(11分制和21分制)
- 每次处理包含几个简单整数运算
- 总操作量约20000次,完全在合理范围内
4.2 常见错误排查
-
未处理最后一局:当比赛不以'E'结束时,最后一局可能未存入results
- 解决方法:循环结束后检查scoreA/scoreB是否>0
-
胜利条件错误:写成
scoreA == 11而非scoreA >= 11- 测试用例:12:10的比分将无法正确识别
-
输入格式错误:忽略空格或非法字符
- 建议:使用
isalpha()过滤或显式检查'W'/'L'/'E'
- 建议:使用
-
输出格式错误:多输出空行或少输出空行
- 建议:严格按照题目示例格式输出
4.3 调试技巧
-
使用小测试用例验证边界条件:
cpp复制void test() { assert(solve("WWL") == "11分制:\n2:1\n\n21分制:\n2:1"); assert(solve("WWWWWWWWWWL") == "11分制:\n11:1\n\n21分制:\n11:1"); assert(solve("WLWLWLWLWLWLWLWLWLWLWLWL") == "11分制:\n11:10\n11:10\n\n21分制:\n21:0"); } -
打印中间状态:
cpp复制cout << "Processing: " << ch << " A=" << scoreA << " B=" << scoreB << endl;
5. 扩展思考与变种题目
5.1 支持自定义计分规则
更通用的实现可以支持任意计分规则:
cpp复制struct Rule {
int winPoint;
int leadPoint;
};
void processMatch(Match& match, char result, const Rule& rule) {
// ... 使用rule.winPoint和rule.leadPoint判断
}
5.2 比赛结果统计分析
可以扩展功能统计:
- 双方最大领先分数
- 连续得分次数
- 关键分(如10:10后)的胜负比例
cpp复制struct Stats {
int maxLead = 0;
int consecutive = 0;
int deuceWins = 0;
};
5.3 可视化输出
使用ASCII艺术输出比赛结果:
code复制PlayerA: ==== 11
PlayerB: == 2
6. 工程实践建议
- 代码组织:将计分规则、比赛状态、IO处理分离到不同类/函数
- 单元测试:为每个核心函数编写测试用例
- 输入验证:严格验证输入格式,防止恶意输入导致崩溃
- 性能优化:对于超长输入可以分批处理(虽然本题不需要)
最终提交的代码应当包含:
- 完整的输入输出处理
- 正确的计分逻辑
- 边界条件处理
- 清晰的代码结构
这个题目虽然归类为"基础题",但完美实现需要考虑诸多细节。我在第一次实现时忽略了10:10后需领先2分的规则,导致提交后只有80%通过率。后来通过添加以下测试用例发现了问题:
cpp复制testCase = "WLWLWLWLWLWLWLWLWLWLWL"; // 11:10
assert(getScore(testCase, 11) == "11:10");
建议每位练习者都自己设计几组极端测试用例,这是提高编程能力的有效方法。
