刚开始做算法题时,最困难的部分往往不是语法,而是把一段自然语言压缩成几个明确条件。Codeforces 的 800 分题很适合训练这项能力:代码通常不长,错误却能准确暴露题意理解、下标和边界处理上的问题。

本文选择四道官方入门题,不复述完整题面,而是专注于“怎样从要求得到判断式”。每题标题都链接到 Codeforces 官方页面,建议先独立阅读和尝试,再回来对照分析。

1. 通用翻译流程

面对一道短题,可以先写出四行草稿:输入是什么、输出是什么、每个条件怎样判断、最小边界在哪里。

不要急着寻找“算法名称”。如果题目只需要扫描、计数或判断,直接实现就是正确算法。

2. 4A Watermelon:必要条件与充分条件

官方题目:4A Watermelon · 难度 800 · 官方标签:mathbrute force

核心要求

把一个整数重量拆成两个正偶数。我们不需要找出具体拆法,只需判断是否存在。

两个偶数之和一定是偶数,所以总重量必须为偶数;但仅仅为偶数还不够,因为最小正偶数是 2,总重量为 2 时无法拆成两个正数。

因此条件可以收敛成:

1
重量是偶数,并且重量大于 2
1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;

int main() {
int weight;
cin >> weight;
cout << ((weight > 2 && weight % 2 == 0) ? "YES" : "NO");
return 0;
}

这道题真正训练的是“必要条件是否已经充分”。偶数是必要条件,排除最小边界后,它才成为充分条件。

3. 71A Way Too Long Words:按规则压缩字符串

官方题目:71A Way Too Long Words · 难度 800 · 官方标签:strings

核心要求

只有长度超过阈值的单词才缩写。缩写由首字符、中间字符数量和末字符组成。

如果字符串长度为 n,被省略的是从第二个字符到倒数第二个字符,一共有 n - 2 个。这是一个常见的“首尾保留,中间计数”结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <string>
using namespace std;

int main() {
int count;
cin >> count;

while (count--) {
string word;
cin >> word;

if (word.size() > 10) {
cout << word.front() << word.size() - 2 << word.back();
} else {
cout << word;
}
cout << '\n';
}
return 0;
}

容易出错的地方是把“超过 10”写成“大于等于 10”。遇到自然语言阈值时,先在纸上列出临界长度 10 和 11,判断哪一个开始变化。

4. 231A Team:把多数意见转成计数

官方题目:231A Team · 难度 800 · 官方标签:brute forcegreedy

核心要求

每道题有三个人分别表示是否确定会做。当至少两个人确定时,团队才实现这道题。

输入使用 0 和 1,所以“三个人中至少两人同意”可以直接变成求和后判断 >= 2,无需写三组逻辑表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

int main() {
int problemCount;
cin >> problemCount;
int solved = 0;

while (problemCount--) {
int first, second, third;
cin >> first >> second >> third;
if (first + second + third >= 2) {
++solved;
}
}

cout << solved;
return 0;
}

这个技巧可以推广到更多投票问题:当状态只有 0 和 1 时,求和就是满足条件的数量。

5. 158A Next Round:阈值与正数条件同时成立

官方题目:158A Next Round · 难度 800 · 官方标签:implementation

核心要求

给定按非递增顺序排列的得分,统计可以晋级的人数。晋级者既要达到第 k 名的分数,也必须取得正分。

题目已经保证分数有序,所以第 k 名对应数组下标 k - 1。扫描所有分数,统计同时满足两个条件的元素即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <vector>
using namespace std;

int main() {
int participantCount, rank;
cin >> participantCount >> rank;

vector<int> scores(participantCount);
for (int &score : scores) {
cin >> score;
}

const int threshold = scores[rank - 1];
int advanced = 0;
for (int score : scores) {
if (score > 0 && score >= threshold) {
++advanced;
}
}

cout << advanced;
return 0;
}

为什么不能只判断 score >= threshold?因为当第 k 名得分为 0 时,后面的 0 分也会满足这个比较,但题目额外要求得分必须为正。

6. 四道题放在一起看

题目 最小思维单元 关键边界
4A 奇偶判断 最小正偶数拆分
71A 字符串长度与首尾访问 长度 10 与 11
231A 0/1 状态计数 至少两人
158A 有序数组扫描 k - 1 下标与 0 分

它们的共同点是:先把题意压缩成一个布尔条件,再让循环只负责重复应用这个条件。

7. 建议的练习方式

  1. 第一次只写条件,不写完整程序;
  2. 主动列出一个最小输入和一个临界输入;
  3. 完成后解释每个判断为什么不能删;
  4. 如果提交错误,先对照题意条件,而不是立刻重写代码;
  5. 四题完成后,再进入固定窗口、双指针和二分查找。

下一篇:Codeforces 序列优化三题:滑动窗口、双指针与二分查找

官方来源