START YOUR ALGORITHM JOURNEY

让每一次思考,
都更接近最优解。

服务于算法竞赛学习者的成长平台。从第一行 C++ 代码开始,用清晰的知识结构、细致的示例和正确的学习方法,陪你一步一步建立解题能力。

14C++ 入门章节
21+代码示例
0零基础门槛
你的学习起点

从这里,走向更好的自己。

知识不是堆得越多越好,重要的是按照正确的顺序真正掌握。

KNOW THE CONTESTS

算法竞赛介绍

了解 ICPC、CCPC、天梯赛、蓝桥杯与百度之星的特点,找到适合自己的参赛方向。

认识常见竞赛
STEP 01

C++ 语法学习

从代码框架、输入输出到数组、结构体和函数,每个知识点都有逐行解释、运行结果与练习。

进入 14 章教程
STEP 02

算法专题学习

基础算法、搜索、数据结构、动态规划与图论,按难度逐步建立完整知识网络。

查看算法目录
PRACTICE

刷题训练

按照 C++ 教程的学习顺序完成洛谷入门题,把刚学会的语法真正变成解题能力。

打开语法题单
ONLINE JUDGES

常用算法学习与刷题平台

教程帮助你理解知识,题目帮助你真正掌握知识。可以根据当前阶段选择一个主平台持续练习。

OJ 是什么?

Online Judge,在线评测系统。提交代码后,系统会自动编译、运行并判断答案是否正确。

学习建议

初学阶段可以先在洛谷或牛客完成基础题;熟悉常用算法后,再定期参加 Codeforces Div. 3/4 或 AtCoder Beginner Contest。不要同时追逐太多平台,持续练习比平台数量更重要。

COURSE 00 · 从这里开始

C++ 竞赛语法入门

写给第一次接触编程的你。我们会从一份完整的代码框架开始,把每个符号、每行代码和运行结果讲清楚。

零基础友好14 个学习章节竞赛场景优先
01

先看懂

从输入、输出和完整代码框架开始,不要求提前掌握任何编程知识。

02

再动手

每学完一个例子,都自己输入、运行并修改数据,观察程序结果。

03

最后刷题

学完对应语法后进入刷题训练,用协会 OJ 和练习题巩固知识。

READY TO START

从配置编程环境开始

第一次学习建议按左侧编号依次完成,已经有基础也可以直接选择章节。

开始第 01 章
CHAPTER 01 · 开始之前

准备编程环境

编程环境就是我们写代码、检查错误、把代码翻译成程序并运行它的一套工具。Windows 初学者可以在下面两种方案中任选一种。

扩展性更强

VS Code + MinGW-w64

VS Code 本身是代码编辑器,不自带 C++ 编译器,需要额外安装编译工具。

  1. 安装 VS Code,并在扩展商店安装微软的 C/C++ 扩展。
  2. 安装 MinGW-w64 工具链,并确认终端能执行 g++ --version
  3. 新建文件夹并用 VS Code 打开,再创建 hello.cpp
  4. 点击编辑器右上角播放按钮,选择检测到的 g++.exe
查看 VS Code 官方配置教程 ↗
i
到底选哪个?

如果你现在只想学习语法,选小熊猫 C++ 最省心;如果你已经熟悉文件路径、终端和扩展,选 VS Code。两者写出的 C++ 代码没有区别。

确认环境是否成功

新建 hello.cpp,复制下面的代码并运行。看到黑色运行窗口中出现 Hello, World! 就说明环境可用。

hello.cpp
#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!";
    return 0;
}
运行结果
Hello, World!
运行失败怎么办?

先检查文件名是否以 .cpp 结尾、代码中的标点是否为英文符号、每条语句末尾是否有分号。VS Code 用户还要确认安装的是 C++ 编译器,而不只是 C/C++ 扩展。

CHAPTER 02 · 程序从哪里开始

第一份代码框架

一份竞赛程序就像一张固定格式的答题纸。刚开始不必背下来,先理解每一部分负责什么。

main.cpp
1#include <bits/stdc++.h>
2using namespace std;
3
4int main() {
5    // 解题代码写在这里
6    return 0;
7}
01

#include 引入工具bits/stdc++.h 会一次性引入算法竞赛常用的标准库,让我们能够使用输入输出、数组容器和排序等工具。

02

using namespace std;让我们可以直接写 cout,不用每次都写完整的 std::cout

03

int main() 是程序入口程序运行时会先找到 main 函数,再从左花括号开始逐行执行。

04

return 0; 正常结束告诉操作系统程序顺利执行完毕。在竞赛代码中通常保留这一行。

先认识三种括号() 圆括号通常放条件或参数;{} 花括号包住一段代码;<> 尖括号在这里包住头文件名。
全角标点是新手高频错误

代码必须使用英文输入法下的 ;(){} 和双引号。中文的 ( ) 无法通过编译。

CHAPTER 03 · 让程序说话

使用 cout 输出

cout 用来把文字或计算结果输出到屏幕。符号 << 可以理解为“把右边的内容送到屏幕”。

输出 Hello World

hello.cpp
cout << "Hello World" << '\n';
运行结果
Hello World

双引号中的内容叫做字符串,会原样输出。'\n' 表示换到下一行,它虽然由两个可见字符组成,但在 C++ 中代表一个换行符。

连续输出多个内容

output.cpp
cout << "答案是:" << 3 + 5 << '\n';
cout << "A" << " " << "B";
运行结果
答案是:8
A B
"A"字符串,用双引号,可以包含多个字符。
'A'单个字符,用单引号,只能表示一个字符。
endl也能换行,但竞赛大量输出时通常使用更快的 '\n'
TRY IT

现在轮到你

请输出两行文字:第一行是你的名字,第二行是 I love C++!。注意两行之间需要换行。

CHAPTER 04 · 接收题目数据

使用 cin 输入

算法题的数据会通过标准输入交给程序。cin 负责读取数据,>> 可以理解为“把输入送进右边的变量”。

读入两个整数并求和

sum.cpp
int a, b;       // 准备两个整数变量
cin >> a >> b;  // 依次读入 a 和 b
cout << a + b << '\n';
输入
12 8
输出
20

空格和换行都可以分隔输入数据。因此输入写成第一行 12、第二行 8,程序仍能正确读取。

输入 12 8a 保存 12b 保存 8输出 20
变量必须先声明再使用

不能直接写 cin >> a; 而没有提前告诉 C++ 变量 a 的类型。这里的 int a; 就是在声明一个整数变量。

TRY IT

计算长方形面积

输入长和宽两个整数,输出它们的乘积。例如输入 4 6,应该输出 24

CHAPTER 05 · 给数据一个名字

变量与数据类型

变量可以想象成带名字的盒子:盒子中保存数据,类型决定这个盒子能装什么、能装多大。

variable.cpp
int age = 15;             // 整数
long long score = 10000000000LL; // 大整数
double pi = 3.14159;     // 小数
char grade = 'A';        // 单个字符
bool passed = true;    // 真或假
string name = "Alice";  // 字符串
类型适合保存竞赛中如何选择
int普通整数绝对值不超过约 21 亿
long long很大的整数乘法、总和或答案可能很大时优先考虑
double带小数的数据平均数、几何计算等
char / string单字符 / 一段文字字符题、字符串题

赋值与修改

change.cpp
int x = 5;
x = 8;      // 把盒子里的 5 换成 8
x = x + 2;  // 读取原来的 8,加 2,再存回 x
cout << x;
运行结果
10

用 sizeof 查看占用空间

sizeof 可以查看一种类型或一个变量占用多少字节(Byte)。它的结果是整数,写类型时通常要加括号,写变量时括号可以省略。

sizeof.cpp
int x = 10;
double price = 3.5;

cout << sizeof(int) << ' ';
cout << sizeof x << ' ';
cout << sizeof(price);
常见运行结果
4 4 8
i
结果可能因环境而不同

多数竞赛环境中 int 占 4 字节、long longdouble 占 8 字节、char 占 1 字节。真正需要确认时,以当前程序的 sizeof 结果为准。

小心整数溢出

100000 * 100000 已超出 int 范围。写成 1LL * 100000 * 100000,或把变量声明为 long long

CHAPTER 06 · 计算与比较

运算符

运算符让程序进行数学计算、比较大小并组合条件。它们是后面判断和循环的基础。

+   -   *   /四则运算整数除法会舍去小数部分:7 / 2 得到 3。
%取余数7 % 2 得到 1,常用来判断奇偶。
==   !=相等 / 不相等比较结果是 true 或 false。
>   <   >=   <=大小比较注意“大于等于”写作 >=
&&   ||   !并且 / 或者 / 取反用来组合多个判断条件。
++   --增加 1 / 减少 1i++ 等价于 i = i + 1

算术运算与整数除法

arithmetic.cpp
int a = 7, b = 2;
cout << a + b << ' ';       // 加法:9
cout << a - b << ' ';       // 减法:5
cout << a * b << ' ';       // 乘法:14
cout << a / b << ' ';       // 整数除法:3
cout << a * 1.0 / b;        // 小数除法:3.5
运行结果
9 5 14 3 3.5

简写赋值与自增自减

update.cpp
int x = 10;
x += 3;  // x = x + 3,现在是 13
x *= 2;  // x = x * 2,现在是 26
x--;     // x = x - 1,现在是 25
cout << x;
运行结果
25

+=-=*=/=%= 都是在原值上计算后再存回变量。单独使用时,++xx++ 都会让 x 加 1;初学阶段不要把它们塞进复杂表达式。

判断一个数是否为偶数

even.cpp
int n;
cin >> n;
cout << (n % 2 == 0);

如果输入 8,表达式 8 % 2 == 0 成立,输出 1;输入 7 时条件不成立,输出 0。

比较与逻辑运算

ticket.cpp
int age;
cin >> age;

bool isTeen = age >= 13 && age <= 18;
bool isFree = age < 6 || age >= 65;
cout << isTeen << ' ' << isFree << ' ';
cout << !isFree;
输入
15
输出
1 0 1

&& 要求两边都成立,|| 只要一边成立,! 会把真假反过来。C++ 默认用 1 表示 true、0 表示 false。

()
不确定优先级就加括号

* / % 通常先于 + - 计算,例如 2 + 3 * 4 得到 14。培训和比赛中都建议用括号主动表达意图:(2 + 3) * 4 得到 20。

=== 完全不同

x = 5 是把 5 存入 x;x == 5 才是在询问“x 是否等于 5”。这是条件判断中最常见的笔误。

CHAPTER 07 · 让程序做选择

if 条件判断

当条件成立时执行一段代码,不成立时执行另一段代码,这就是分支结构。

score.cpp
int score;
cin >> score;

if (score >= 90) {
    cout << "优秀";
} else if (score >= 60) {
    cout << "及格";
} else {
    cout << "继续努力";
}
输入 95score ≥ 90 ✓输出“优秀”
输入 75第一个条件 ×,score ≥ 60 ✓输出“及格”
输入 40两个条件都不成立进入 else

else if 会按从上到下的顺序检查。一旦某个条件成立并执行,对应的整组判断就结束了,因此更严格的条件通常写在前面。

组合条件

range.cpp
if (age >= 13 && age <= 18) {
    cout << "青少年";
}

多个固定选项:switch

当变量只需要和几个固定值比较时,可以使用 switch。每个 case 表示一种情况,default 处理其他情况。

weekday.cpp
int day;
cin >> day;

switch (day) {
    case 1:
        cout << "Monday";
        break;
    case 2:
        cout << "Tuesday";
        break;
    default:
        cout << "Other day";
}
输入 2 后输出
Tuesday
不要漏掉 break

执行某个 case 后,break 会离开整个 switch。如果漏写,程序会继续执行后面的 case,这种现象称为“贯穿”。

TRY IT

判断正负

输入一个整数。大于 0 输出 positive,等于 0 输出 zero,小于 0 输出 negative

CHAPTER 08 · 重复固定次数

for 循环

当你知道一段代码需要执行多少次,for 循环通常最合适。它把“从哪里开始、何时继续、每次怎样变化”写在同一行。

for (int i = 1; i <= 5; i++)
只在开始时执行一次每轮开始前检查每轮结束后执行
count.cpp
for (int i = 1; i <= 5; i++) {
    cout << i << ' ';
}
运行结果
1 2 3 4 5

循环是怎样运行的?

轮次i 的值i ≤ 5?执行结果
11输出 1,i++
22输出 2,i++
继续重复
66循环结束

经典例子:求 1 到 n 的和

sum_n.cpp
int n, sum = 0;
cin >> n;

for (int i = 1; i <= n; i++) {
    sum += i; // 等价于 sum = sum + i
}
cout << sum;
输入
5
输出
15

提前结束或跳过本轮:break 与 continue

break 会立刻结束整个循环;continue 只跳过当前这一轮,随后进入下一轮。它们既可以用在 for 中,也可以用在 while 中。

loop_control.cpp
for (int i = 1; i <= 10; i++) {
    if (i == 8) break;       // 到 8 时结束循环
    if (i % 2 == 0) continue; // 偶数跳过输出
    cout << i << ' ';
}
运行结果
1 3 5 7

i 为 2、4、6执行 continue,本轮后面的输出语句不再执行。

i 为 8执行 break,整个循环结束,9 和 10 也不会再处理。

嵌套循环:循环里面再写循环

外层循环每执行一轮,内层循环都会从头完整执行。常用于打印图形、枚举行列、处理二维数组。

rectangle.cpp
for (int row = 1; row <= 3; row++) {
    for (int col = 1; col <= 4; col++) {
        cout << '*';
    }
    cout << '\n';
}
运行结果
****
****
****

row 控制行外层共执行 3 轮,因此输出 3 行。

col 控制列每一行中内层执行 4 轮,因此每行输出 4 个星号。

循环体共执行 3 × 4 次若两层分别循环 n 次和 m 次,总次数就是 n × m。

break 只结束它所在的那一层循环

在内层循环执行 break,外层循环仍会继续下一轮。如果需要同时结束两层,通常使用布尔标记,或把这段逻辑封装进函数后用 return

边界差一位(Off-by-one)

i <= n 会包含 n,循环 n 次;i < n 不包含 n,只到 n - 1。写循环前先在纸上明确第一个值和最后一个值。

TRY IT

输出所有偶数

输入 n,输出 1 到 n 之间的所有偶数。你可以让 i 每次加 1 后判断,也可以思考怎样让 i 每次直接加 2。

CHAPTER 09 · 条件成立就继续

while 循环

当循环次数不确定,但“继续执行的条件”很清楚时,使用 while。每一轮开始前,程序都会先检查括号中的条件。

digits.cpp
int n;
cin >> n;

while (n > 0) {
    cout << n % 10 << ' ';
    n /= 10;
}
输入 1234 后输出
4 3 2 1

n % 10取得十进制个位数。

n /= 10删掉个位数,让 n 逐步变为 123、12、1、0。

n > 0当 n 变成 0 时条件不成立,循环结束。

在 while 中使用 break 与 continue

下面的程序不断读入整数:遇到负数就跳过,遇到 0 就结束,其他数累加。循环次数由输入内容决定,所以很适合使用 while

read_until_zero.cpp
int sum = 0;

while (true) {
    int x;
    cin >> x;
    if (x == 0) break;    // 结束整个 while
    if (x < 0) continue; // 跳过负数,继续读下一个
    sum += x;
}
cout << sum;
输入
5 -2 7 0
输出
12
i
while (true) 为什么能结束?

它本身是无限循环,但读到 0 时会执行 break。这种“先循环、满足条件再退出”的写法在处理未知数量的输入时很常见。

至少执行一次:do while

do_while.cpp
int x;
do {
    cin >> x;
} while (x < 0); // 别漏掉最后的分号

while 是先判断再执行,可能一次也不执行;do while 是先执行再判断,因此循环体至少执行一次。

警惕死循环

循环体必须让条件逐渐接近“不成立”。如果漏掉 n /= 10,n 永远不变,程序就会一直运行。遇到这种情况可以手动停止程序。

continue 前要保证状态发生变化

在手动维护循环变量的 while 中,如果先执行 continue,后面的 i++ 就会被跳过,程序可能永远停在同一个值。可以把更新语句放到判断之前,或改用 for

for 还是 while?

for遍历 1 到 n、重复固定次数、遍历数组。
while不断读入直到遇到 0、数字拆位、次数事先未知。
CHAPTER 10 · 保存一组数据

数组

如果要保存 100 个整数,没必要创建 100 个不同名字的变量。数组用一个名字管理一组类型相同的数据。

下标01234
数值83619
!
下标从 0 开始

长度为 5 的数组,下标是 0、1、2、3、4。最后一个元素是 a[4],访问 a[5] 已经越界。

读入 n 个数并求最大值

maximum.cpp
int n;
cin >> n;
int a[1005];

for (int i = 0; i < n; i++) {
    cin >> a[i];
}

int answer = a[0];
for (int i = 1; i < n; i++) {
    answer = max(answer, a[i]);
}
cout << answer;
输入
5
8 3 6 1 9
输出
9

int a[1005] 提前准备 1005 个位置。实际使用前 n 个位置,也就是 a[0]a[n-1]

数组的初始化

initialize.cpp
int a[5] = {8, 3, 6, 1, 9};
int zero[100] = {}; // 所有元素初始化为 0

for (int i = 0; i < 5; i++) {
    cout << a[i] << ' ';
}

局部数组如果没有初始化,里面的值是不确定的,不能直接拿来累加或比较。计数数组常用 {} 将所有位置清零。

二维数组:按行和列保存数据

int a[3][4] 可以看成 3 行 4 列的表格。访问时先写行下标,再写列下标,例如 a[1][2] 表示第 2 行第 3 列。

matrix.cpp
int n, m;
cin >> n >> m;
int a[105][105];

for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
        cin >> a[i][j];
    }
}

int sum = 0;
for (int j = 0; j < m; j++) {
    sum += a[0][j]; // 累加第 1 行
}
cout << sum;
输入
2 3
1 2 3
4 5 6
输出
6

i = 0 ... n-1外层循环枚举每一行。

j = 0 ... m-1内层循环枚举这一行的每一列。

a[i][j]表示第 i + 1 行、第 j + 1 列的元素。

TRY IT

寻找二维数组最大值

输入 n 行 m 列的整数矩阵,使用两层循环找出最大值,并输出它所在的行号和列号。

不要访问数组边界之外

数组越界不一定立即报错,但会读写不属于它的内存,导致答案错误甚至程序崩溃。竞赛中应始终检查循环边界。

CHAPTER 11 · 处理文字

字符串 string

string 用来保存一串字符。它很像一个字符数组,同样可以通过从 0 开始的下标访问每个字符。

string.cpp
string s;
cin >> s;

cout << s.size() << '\n';
cout << s[0] << '\n';

for (char c : s) {
    cout << c << ' ';
}
输入 code 后输出
4
c
c o d e
cin >> s读取到空格就停止,适合单个单词。
getline(cin, s)读取完整一行,能够包含空格。
s.size()取得字符串长度,结果是字符数量。

判断回文串

palindrome.cpp
bool ok = true;
for (int i = 0; i < s.size(); i++) {
    if (s[i] != s[s.size() - 1 - i]) {
        ok = false;
    }
}

第 i 个字符与倒数第 i 个字符比较。注意最后一个字符下标是 s.size() - 1

CHAPTER 12 · 把相关数据放在一起

结构体 struct

结构体可以把多个不同类型、但彼此相关的数据组合成一个整体。竞赛中常用它表示学生、坐标、边、区间或题目记录。

定义一种新的数据结构

student.cpp
struct Student {
    string name;
    int score;
    int age;
}; // 结构体定义结束后需要分号

Student我们创建的新类型名称,以后可以像 int 一样用它声明变量。

成员变量name、score 和 age 描述一名学生的不同信息。

末尾分号结构体右花括号后必须写分号,这是非常常见的编译错误。

创建变量并访问成员

use_struct.cpp
Student a;
a.name = "Alice";
a.score = 95;
a.age = 16;

cout << a.name << ' ' << a.score;
运行结果
Alice 95

点号 . 表示访问结构体中的某个成员。例如 a.score 就是“学生 a 的分数”。

结构体数组:保存多名学生

students.cpp
int n;
cin >> n;
Student students[105];

for (int i = 0; i < n; i++) {
    cin >> students[i].name >> students[i].score;
}

int best = 0;
for (int i = 1; i < n; i++) {
    if (students[i].score > students[best].score) {
        best = i;
    }
}
cout << students[best].name;
输入
3
Alice 95
Bob 88
Carol 97
输出
Carol
TRY IT

表示二维坐标

定义结构体 Point,包含整数成员 x 和 y。输入两个点,输出它们横坐标之和与纵坐标之和。

CHAPTER 13 · 给一段逻辑命名

函数

函数把一段可以重复使用的逻辑封装起来。它可以接收参数,完成计算,再把结果返回给调用者。

int返回值类型
maximum函数名称
(int a, int b)参数列表
function.cpp
int maximum(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}

int main() {
    int answer = maximum(7, 12);
    cout << answer; // 输出 12
    return 0;
}
调用 maximum(7, 12)a=7,b=12返回 12answer=12
int f()函数会返回一个整数,需要写 return。
bool f()函数返回 true 或 false,常用于判断。
void f()函数不返回结果,只执行某些操作。

参数与返回值

调用函数时,括号中传入的是实参;函数定义中接收数据的 a、b 是形参。普通参数会复制一份数据,修改形参不会影响外面的变量。

parameter.cpp
void addOne(int x) {
    x++;
}

int main() {
    int n = 5;
    addOne(n);
    cout << n;
}
运行结果
5

引用传参:修改调用者的变量

在参数类型后加 & 表示引用。此时参数是外部变量的别名,对它的修改会保留下来。

reference.cpp
void swapValue(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

int x = 3, y = 8;
swapValue(x, y);
cout << x << ' ' << y;
运行结果
8 3
int x值传递:复制一份,函数内修改不影响原变量。
int &x引用传递:直接操作原变量,可以把修改带出函数。
const string &s只读引用:避免复制较大的数据,同时禁止函数修改它。

void 函数与提前 return

print_positive.cpp
void printPositive(int x) {
    if (x <= 0) {
        return; // 立即结束函数,不返回具体数值
    }
    cout << x;
}

void 函数不返回计算结果,但仍可以用单独的 return; 提前结束。一个有返回值的函数则要保证每条可能执行的路径都能返回正确类型的值。

声明顺序与函数声明

C++ 需要先知道函数长什么样,才能调用它。可以把完整函数写在 main 前,也可以先写函数声明,再把实现放到 main 后。

declaration.cpp
int square(int x); // 函数声明:末尾有分号

int main() {
    cout << square(6);
}

int square(int x) { // 函数实现
    return x * x;
}
局部变量只在函数内部有效

在函数的花括号中声明的变量,离开函数后就不能再访问。不同函数可以拥有同名的局部变量,它们互不影响。

TRY IT

封装一个判断函数

编写 bool isPrime(int n),判断 n 是否为质数。在 main 中读入一个整数,根据函数返回值输出 YesNo

CHAPTER 14 · 竞赛常用工具

STL 常用工具

STL 是 C++ 标准库提供的一套现成工具。入门阶段先掌握 vectorsort,以及 maxminswap 等常用函数。

vector:长度可变化的数组

vector.cpp
vector<int> a;
a.push_back(8); // 在末尾加入 8
a.push_back(3); // 在末尾加入 3
a.push_back(6); // 在末尾加入 6

cout << a.size(); // 输出 3

sort:从小到大排序

sort.cpp
int n;
cin >> n;
vector<int> a(n);

for (int i = 0; i < n; i++) cin >> a[i];

sort(a.begin(), a.end());

for (int x : a) cout << x << ' ';
输入
5
8 3 6 1 9
输出
1 3 6 8 9

vector<int> a(n)创建长度为 n 的整数 vector。

a.begin()指向第一个元素的位置。

a.end()指向最后一个元素后面的位置,sort 排序范围左闭右开。

max、min 与 swap

max 取较大值,min 取较小值,swap 交换两个变量的值。它们能让常见操作写得更直接。

basic_tools.cpp
int a = 8, b = 3;

cout << max(a, b) << ' '; // 较大值
cout << min(a, b) << '\n'; // 较小值

swap(a, b);
cout << a << ' ' << b;
运行结果
8 3
3 8
i
它们是标准库函数,不是 C++ 关键字

使用标准头文件时,maxmin 位于 <algorithm>swap 可由 <utility> 提供。竞赛中常用的 #include <bits/stdc++.h> 已经包含这些头文件。

max 和 min 的两个参数通常要类型一致

例如 max(3, 4LL) 的一个参数是 int、另一个是 long long,可能无法编译。可以写成 max(3LL, 4LL),让两边类型保持一致。

处理数组和 vector 的常用函数

reverse翻转顺序reverse(a.begin(), a.end()) 将整个 vector 前后颠倒。
count统计出现次数count(a.begin(), a.end(), 3) 统计 3 出现了几次。
find寻找元素找不到时返回 a.end(),使用前要先判断。
max_element寻找最大元素返回元素所在位置,前面加 * 取得数值。
min_element寻找最小元素用法与 max_element 相同。
fill批量赋值fill(a.begin(), a.end(), 0) 把所有元素改成 0。
vector_tools.cpp
vector<int> a = {8, 3, 6, 3, 9};

cout << count(a.begin(), a.end(), 3) << '\n';
cout << *max_element(a.begin(), a.end()) << '\n';

reverse(a.begin(), a.end());
for (int x : a) cout << x << ' ';
原始数据
8 3 6 3 9
输出
2
9
9 3 6 3 8
不要对空容器解引用

如果 vector 为空,max_element 会返回 a.end(),此时不能在前面加 *。应先用 a.empty() 判断容器中是否有元素。

数值处理中也很常用

abs(x)取得绝对值,例如 abs(-7) 得到 7。
sqrt(x)计算平方根,返回小数,例如 sqrt(16) 得到 4。
pow(a, b)计算 a 的 b 次方;整数幂在竞赛中常用循环计算,避免浮点误差。
TRY IT

使用标准库处理一组数

读入 n 个整数,输出其中的最大值、最小值和某个指定数字出现的次数,然后将整个序列逆序输出。

你已经具备写基础竞赛程序的语法能力

下一步不要急着学习更多语法。先用这些知识完成求和、最大值、统计、模拟等基础题目,让输入—计算—输出的过程变得熟练。

PRACTICE MAKES PROGRESS

刷题训练

教程负责把知识讲明白,题目负责让知识真正属于你。这里按照 C++ 语法课程的顺序整理入门题目,学完一章就能立刻练习。

32 道洛谷训练题3—5 分 手动完成积分协会 OJ AC 自动积分
我的刷题进度0 / 0

洛谷题可手动标记完成,协会 OJ 题需判题通过。当前积分:0

推荐做题方式

先独立思考,再运行样例,最后提交。

  1. 01读清输入和输出
  2. 02在纸上写出计算过程
  3. 03自己输入代码并调试
STAGE 01 · 对应教程 01—03

输入、输出与第一份程序

熟悉完整代码框架、coutcin 和最基本的计算。

5 题
01
洛谷 · B2002输出

Hello,World!

写出第一份完整程序,使用 cout 输出指定文字。

去做题 ↗
02
洛谷 · P1000多行输出

超级玛丽游戏

练习按题目要求准确输出多行字符图案。

去做题 ↗
03
洛谷 · P1001输入输出

A+B Problem

读入两个整数,计算并输出它们的和。

去做题 ↗
04
洛谷 · P5703乘法

苹果采购

根据人数和每人分到的数量,计算需要采购的苹果总数。

去做题 ↗
05
洛谷 · P5704字符

字母转换

读入一个小写字母,理解字符处理与输出。

去做题 ↗
STAGE 02 · 对应教程 04—05

变量、数据类型与运算符

练习整数、小数、字符以及常用算术运算。

5 题
06
洛谷 · P5705小数

数字反转

读取一个小数并按相反顺序输出,熟悉数据表现形式。

去做题 ↗
07
洛谷 · P5706除法

再分肥宅水

练习浮点数除法与整数数量计算。

去做题 ↗
08
洛谷 · P5708公式

三角形面积

把数学公式翻译成 C++ 表达式,注意小数精度。

去做题 ↗
09
洛谷 · P1425时间计算

小鱼的游泳时间

使用整除和取余处理小时与分钟。

去做题 ↗
10
洛谷 · P3954加权计算

成绩

按照比例计算综合成绩,巩固表达式和类型转换。

去做题 ↗
STAGE 03 · 对应教程 06

if 与条件判断

根据不同输入选择不同执行路径,练习逻辑表达式。

5 题
11
洛谷 · P5710逻辑运算

数的性质

综合使用与、或、非,判断一个整数具有哪些性质。

去做题 ↗
12
洛谷 · P5711复合条件

闰年判断

把闰年规则写成准确的布尔表达式。

去做题 ↗
13
洛谷 · P5713方案比较

洛谷团队系统

计算两种方案的代价,再根据结果做出选择。

去做题 ↗
14
洛谷 · P5716多分支

月份天数

综合年份和月份,判断当月具有多少天。

去做题 ↗
15
洛谷 · P5717分类讨论

三角形分类

对边长排序并进行多种条件判断,训练分类顺序。

去做题 ↗
STAGE 04 · 对应教程 07—08

for 与 while 循环

让程序重复工作,完成统计、累加和过程模拟。

5 题
16
洛谷 · P5718遍历

找最小值

循环读入一组数字,在遍历过程中维护最小值。

去做题 ↗
17
洛谷 · P5720while

一尺之棰

不断让长度减半,统计需要多少天。

去做题 ↗
18
洛谷 · P5721嵌套循环

数字直角三角形

使用两层循环控制行、列和连续编号。

去做题 ↗
19
洛谷 · P5722累加

数列求和

使用循环累加 1 到 n,理解累加器变量。

去做题 ↗
20
洛谷 · P5723循环判断

质数口袋

在预算范围内寻找质数,综合循环、条件与累计。

去做题 ↗
STAGE 05 · 对应教程 09—10

数组与字符串

保存一批数据,并按下标访问、统计或修改它们。

6 题
21
洛谷 · P1427数组

小鱼的数字游戏

把输入保存到数组,再按照相反顺序输出。

去做题 ↗
22
洛谷 · P5727数组 + while

冰雹猜想

模拟数值变化过程并保存结果,最后倒序输出。

去做题 ↗
23
洛谷 · P5728二维数据

旗鼓相当的对手

保存多名同学的成绩,并比较每一对同学。

去做题 ↗
24
洛谷 · P5730字符图形

显示屏

使用数组保存数字图案,按行组合并输出。

去做题 ↗
25
洛谷 · P5733字符转换

自动修正

遍历字符串,把其中的小写字母转换为大写。

去做题 ↗
26
洛谷 · P5734string 操作

文字处理软件

练习字符串插入、截取、查找等常用操作。

去做题 ↗
STAGE 06 · 对应教程 11—13

函数、结构体与排序

拆分重复逻辑、组织复合数据,并使用标准库完成排序。

6 题
27
洛谷 · P5735函数

距离函数

把两点距离封装成函数,重复调用并求和。

去做题 ↗
28
洛谷 · P5736布尔函数

质数筛

编写判断质数的函数,过滤并输出符合条件的数字。

去做题 ↗
29
洛谷 · P5737函数复用

闰年展示

复用闰年判断函数,输出区间中的所有闰年。

去做题 ↗
30
洛谷 · P5738数据处理

歌唱比赛

封装评分计算,去掉最高分和最低分后求平均值。

去做题 ↗
31
洛谷 · P5740结构体

最厉害的学生

用结构体保存姓名和三科成绩,寻找总分最高者。

去做题 ↗
32
洛谷 · P5715sort

三位数排序

把三个数放入容器并从小到大输出,练习 sort。

去做题 ↗
题目来源说明

本页题目主要选自洛谷《深入浅出程序设计竞赛》官方入门题单。完成后可手动标记并获得 3—5 积分;协会 OJ 题仍按 Hydro 实际判题结果自动计分。

查看洛谷官方题单 ↗
ASSOCIATION OJ · GRAMMAR

协会 OJ · 语法训练题

这些题目由协会 Hydro 自动判题,AC 后本站会自动点亮并计入积分。

正在加载协会语法题…
COURSE 00 · 建立算法知识体系

算法学习

语法告诉我们代码怎样写,算法告诉我们问题应该怎样解决。这里会从读懂题目开始,逐步建立复杂度意识,再进入动态规划、图论和数论。

01

基础算法

读题、复杂度、模拟、贪心、二分、前缀和与差分。

当前学习重点
02

动态规划

学习状态设计、转移方程、递推顺序与空间优化。

路线已规划
03

图论

从图的存储与遍历开始,进入最短路和生成树。

路线已规划
04

数论

掌握整除、质数、快速幂和同余等竞赛工具。

路线已规划
RECOMMENDED START

先完成基础算法

先建立读题和复杂度意识,再学习具体算法会更稳。

TRACK 01 · FOUNDATION

基础算法路线

这一阶段先建立正确的解题流程和复杂度意识,再学习模拟、贪心、二分、前缀和与差分。

读懂题目分析复杂度掌握算法思想独立完成题目
FOUNDATION 01

从读懂题目格式开始

先把题目描述翻译成清晰的输入、计算和输出任务。

开始第一节
FOUNDATION 04 · 按题意还原过程

模拟

模拟题不要求先套一个复杂算法,而是把题目描述中的状态、规则和操作顺序准确翻译成程序。关键是先写清“现在保存什么”“每一步怎样变化”“什么时候结束”。

01

确定状态

找出过程中会发生变化的数据,例如当前位置、当前时间、剩余数量或字符串下标。

02

拆分步骤

按照题意把一次操作拆成固定顺序,避免更新先后颠倒。

03

处理边界

重点检查第一步、最后一步、空输入、越界以及日期进位等特殊情况。

04

验证样例

在纸上逐步记录状态,与程序每一步的结果进行对照。

示例:按指令移动位置

从位置 0 出发,依次读取字符。遇到 L 向左移动一格,遇到 R 向右移动一格,最后输出位置。

simulation.cpp
string commands;
cin >> commands;
int position = 0;

for (char command : commands) {
    if (command == 'L') position--;
    else if (command == 'R') position++;
}
cout << position << '\n';
模拟不是“想到哪写到哪”

规则越多,越应该先列出状态和操作顺序。更新多个变量时,要确认后一步使用的是更新前还是更新后的值。

TRY IT

日期推进一天

输入一个合法日期,输出它的下一天。分别考虑月末、年末以及闰年二月。

FOUNDATION 05 · 做出当前最优选择

贪心

贪心算法在每一步选择当前看来最优的方案,并且不回头修改。真正的难点不是写出选择,而是说明这个局部选择为什么不会让最终答案变差。

01

明确目标

先确认要最小化、最大化或尽可能多地完成什么。

02

设计选择

常见策略包括按结束时间、代价、收益或某个比值排序。

03

寻找反例

用很小的数据尝试推翻策略,能被反例推翻的就不是正确贪心。

04

交换论证

证明任意最优解都能调整为包含当前选择,且答案不会变差。

经典例子:选择最多不重叠区间

把区间按照结束位置从小到大排序,每次选择第一个与已选区间不冲突的区间。越早结束,就为后面的区间留下越多空间。

interval_greedy.cpp
sort(intervals.begin(), intervals.end(), [](auto a, auto b) {
    return a.second < b.second;
});

int answer = 0, lastEnd = -INF;
for (auto [left, right] : intervals) {
    if (left >= lastEnd) {
        answer++;
        lastEnd = right;
    }
}
i
排序通常是贪心的第一步

排序把候选方案放到一个有利顺序中,但“排序后取第一个”仍然需要正确性说明。

TRY IT

安排最多活动

给出若干活动的开始与结束时间,选择数量最多且互不重叠的一组活动。

FOUNDATION 01 · 解题的第一步

读懂题目格式

一道算法题通常由题目描述、输入格式、输出格式、数据范围和样例组成。很多错误不是算法不会,而是没有把输入、输出或数据范围读准确。

01

题目描述

说明需要解决什么问题。先把故事背景翻译成一句明确任务,例如“求区间和”或“寻找第一个满足条件的位置”。

02

输入格式

说明程序会读到哪些数据、每个数据的含义以及排列顺序。变量声明和循环次数都来自这里。

03

输出格式

说明最终要输出什么。空格、换行、保留小数位数以及输出顺序都可能影响评测结果。

04

数据范围

决定数据类型和算法复杂度。看到 n ≤ 10^5,通常就不能使用 O(n²)

示例:读入两个整数并求和

题目描述

给定两个整数 ab,输出它们的和。

输入格式

一行两个整数 a, b,以空格分隔。

输出格式

输出一个整数,表示 a + b

数据范围

|a|, |b| ≤ 10⁹

solution.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    long long a, b;
    cin >> a >> b;
    cout << a + b << '\n';
    return 0;
}
输入
12 8
输出
20
提交前检查
是否读入了所有数据?
输出内容和顺序是否完全正确?
数据类型能否容纳最大答案?
是否考虑最小值、最大值和特殊情况?
FOUNDATION 02 · 程序能否按时完成

时间复杂度

时间复杂度描述输入规模 n 增大时,程序操作次数增长得有多快。它不直接等于运行秒数,而是帮助我们在写代码之前判断算法是否可能超时。

O(1)常数直接计算
O(log n)对数二分查找
O(n)线性遍历数组
O(n log n)线性对数高效排序
O(n²)平方两层枚举

怎样从代码看出复杂度?

O(1)执行次数与 n 无关
int answer = a[0] + a[n - 1];
O(n)一层循环执行 n 次
for (int i = 0; i < n; i++)
    sum += a[i];
O(n²)两层循环各执行约 n 次
for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++)
        check(i, j);

根据数据范围估计算法

输入规模 n通常可以考虑直观理解
n ≤ 20指数级、状态枚举可以尝试枚举许多组合
n ≤ 500O(n³)三层循环需要谨慎
n ≤ 5,000O(n²)大约数千万次操作
n ≤ 10⁵O(n log n)O(n)排序、二分、线性遍历
n ≤ 10⁷O(n)通常只能做少量遍历
估算不是绝对规则

实际速度还受到常数、语言、内存访问和评测机影响。竞赛中常用“约一秒执行一亿次简单操作”进行粗略判断,但不能把它当成精确保证。

FOUNDATION 03 · 程序需要多少内存

空间复杂度

空间复杂度描述算法额外使用的存储空间怎样随输入规模增长。数组、容器、递归调用栈都会占用内存;超过题目的内存限制会得到 MLE。

单个变量O(1)

无论 n 多大,只使用固定数量的变量。

int sum, maximum;
一维数组O(n)

保存 n 个同类型元素,空间随 n 线性增长。

vector<int> a(n);
二维数组O(n²)

保存 n × n 个元素,n 增大时内存增长很快。

int grid[n][n];

把元素数量换算成内存

常见的 int 通常占 4 字节,long long 通常占 8 字节。估算数组内存时,可以使用:

数组内存元素个数 × 每个元素的字节数

int a[1'000'000] 大约占用 1,000,000 × 4 B ≈ 3.8 MB

256 MB 大约能保存6,700 万个 int实际程序还需要其他内存,不能把限制全部用满
10000 × 10000 的 int 数组约 381 MB在 256 MB 限制下会超出内存
i
原地算法

如果算法直接在输入数组上修改数据,只使用少量额外变量,它的额外空间可能是 O(1)。分析时要区分“输入本身占用的空间”和“算法额外申请的空间”。

FOUNDATION 06 · 每次排除一半答案

二分查找

二分查找利用数据的有序性或答案的单调性,每次检查中间位置并排除一半范围,把线性查找的 O(n) 降低为 O(log n)

使用二分前必须确认

搜索范围具有单调性

例如数组已经从小到大排序;或者某个条件在一段范围内为 false,之后全部为 true。

falsefalsefalse边界truetruetrue

示例:在有序数组中寻找 13

下标0123456
数值25813172130

第 1 次:left = 0, right = 6, mid = 3

发现 a[3] = 13,目标找到。

查找某个值是否存在

binary_search.cpp
bool exists(const vector<int>& a, int target) {
    int left = 0;
    int right = (int)a.size() - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (a[mid] == target) return true;
        if (a[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return false;
}

left <= right闭区间 [left, right] 还有元素时继续查找。

left + (right-left)/2(left+right)/2 含义相同,但能够避免两数相加溢出。

更新时越过 mid已经检查过 mid,所以使用 mid+1mid-1,否则可能死循环。

二分最常见的错误

区间定义混乱。写代码前先决定使用闭区间 [left, right] 还是左闭右开区间 [left, right),循环条件和更新方式必须始终与它保持一致。

FOUNDATION 07 · 快速回答区间求和

前缀和

如果需要反复询问数组某个区间的元素总和,每次从左到右重新累加会很慢。前缀和先进行一次 O(n) 预处理,之后每次区间查询只需 O(1)

前缀和数组表示什么?

定义 prefix[i] 表示原数组前 i 个元素之和。为了让公式更整齐,令 prefix[0] = 0

原数组 a31415
逐项累加 ↓
prefix0348914
预处理公式prefix[i] = prefix[i - 1] + a[i]

把第 i 个元素加入前 i-1 个元素的总和。

区间 [l, r] 的和prefix[r] - prefix[l - 1]

前 r 个元素之和,减去 l 之前的所有元素。

为什么区间公式成立?

求数组第 2 到第 4 个元素之和:

3 + 1 + 4 + 1− 3= 1 + 4 + 1 = 6
prefix[4] - prefix[1] = 9 - 3 = 6
prefix_sum.cpp
int n, q;
cin >> n >> q;

vector<long long> prefix(n + 1, 0);
for (int i = 1; i <= n; i++) {
    long long x;
    cin >> x;
    prefix[i] = prefix[i - 1] + x;
}

while (q--) {
    int left, right;
    cin >> left >> right;
    cout << prefix[right] - prefix[left - 1] << '\n';
}
预处理时间O(n)
单次查询O(1)
q 次总时间O(n + q)
额外空间O(n)
注意 long long

即使数组中的每个元素都能放进 int,许多元素相加后的前缀和也可能超过 int 范围。只要总和可能很大,就使用 long long

EXTENSION · 从一条线推广到一个矩形

二维前缀和

二维前缀和用于快速计算矩阵中的矩形区域和。它是一维前缀和的自然推广:prefix[i][j] 表示从左上角 (1, 1) 到右下角 (i, j) 的整个矩形元素之和。

适用场景

多次询问矩阵中的矩形和

例如地图区域统计、二维棋盘计数、图片像素区域求和等。

12345 23456 34567 45678

怎样构造二维前缀和?

构造公式 prefix[i][j] = a[i][j] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1]

上方矩形与左方矩形都包含左上角重叠区域,因此相加后必须把 prefix[i-1][j-1] 减去一次。

当前位置的值a[i][j]
加上上方区域prefix[i-1][j]
加上左方区域prefix[i][j-1]
减去重复区域prefix[i-1][j-1]

怎样查询任意矩形?

设矩形左上角为 (x1, y1),右下角为 (x2, y2),包含边界。先取右下角的大前缀矩形,再减去上方和左方多余区域,最后把被重复减去的左上区域加回来。

矩形查询公式 prefix[x2][y2] - prefix[x1-1][y2] - prefix[x2][y1-1] + prefix[x1-1][y1-1]
取全部− 上方− 左方+ 重叠
prefix_sum_2d.cpp
int n, m, q;
cin >> n >> m >> q;

vector<vector<long long>> prefix(
    n + 1, vector<long long>(m + 1, 0)
);

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
        long long value;
        cin >> value;
        prefix[i][j] = value
            + prefix[i - 1][j]
            + prefix[i][j - 1]
            - prefix[i - 1][j - 1];
    }
}

while (q--) {
    int x1, y1, x2, y2;
    cin >> x1 >> y1 >> x2 >> y2;
    long long answer = prefix[x2][y2]
        - prefix[x1 - 1][y2]
        - prefix[x2][y1 - 1]
        + prefix[x1 - 1][y1 - 1];
    cout << answer << '\n';
}
预处理时间O(nm)
单次查询O(1)
q 次总时间O(nm + q)
额外空间O(nm)
二维前缀和最容易错在下标

建议让矩阵下标从 1 开始,并额外保留第 0 行和第 0 列为 0。这样查询贴着上边界或左边界的矩形时,不需要额外分类讨论。

FOUNDATION 08 · 快速完成区间修改

差分

如果要对数组的许多区间整体加上一个数,逐个修改区间中的每个元素会很慢。差分只修改区间的两个边界,每次操作是 O(1),最后再用一次前缀和还原整个数组。

差分数组表示什么?

diff[i] = a[i] - a[i - 1],并规定 a[0] = 0。差分记录的是“当前位置相对前一个位置变化了多少”。

原数组 a31415
相邻元素作差 ↓
diff3-23-340

最后一个 0 是额外预留的 diff[n + 1],用于处理右端点恰好为 n 的区间修改。它不属于原数组。

构造差分diff[i] = a[i] - a[i - 1]

记录相邻元素之间的变化量。

还原原数组a[i] = a[i - 1] + diff[i]

对 diff 求前缀和,就能重新得到 a。

区间 [l, r] 全部加上 k

从 l 开始增加 kdiff[l] += k

表示从位置 l 起,后面的元素都多出 k。

从 r + 1 取消影响diff[r + 1] -= k

让这次增加只影响到 r,不继续传到后面。

原数组为 3 1 4 1 5,将区间 [2, 4] 全部加 2:

diff[2] += 2diff[5] -= 2还原后:3 3 6 3 5
区间内部不需要逐个修改,只改变开始位置和结束位置的下一格。

完整模板:多次区间加

difference.cpp
int n, m;
cin >> n >> m;

vector<long long> diff(n + 2, 0);
long long previous = 0;

for (int i = 1; i <= n; i++) {
    long long value;
    cin >> value;
    diff[i] = value - previous;
    previous = value;
}

while (m--) {
    int left, right;
    long long value;
    cin >> left >> right >> value;
    diff[left] += value;
    diff[right + 1] -= value;
}

for (int i = 1; i <= n; i++) {
    diff[i] += diff[i - 1];
    cout << diff[i] << ' ';
}
输入
5 2
3 1 4 1 5
2 4 2
1 3 -1
输出
2 2 5 3 5
构造差分O(n)
单次区间修改O(1)
m 次修改并还原O(n + m)
额外空间O(n)
前缀和擅长“数组不变,多次查询区间和”。
差分擅长“多次修改区间,最后得到整个数组”。
两者关系差分的前缀和是原数组,原数组的相邻差是差分。
记得为 r + 1 多开一个位置

如果数组使用 1 到 n 的下标,差分数组至少开到 n + 1。代码中常写 vector<long long> diff(n + 2),避免访问 diff[right + 1] 时越界。

普通差分适合离线得到最终结果

如果每次修改后都要立刻查询当前区间和,不能每次重新还原数组;这类在线问题通常需要树状数组或线段树。

EXTENSION · 从区间修改推广到矩形修改

二维差分

二维差分可以把矩形 (x1, y1)(x2, y2) 内的所有元素同时加上 value。一次修改只需要改变四个角,最后对差分矩阵求二维前缀和即可还原。

矩形修改公式 diff[x1][y1] += v  diff[x2+1][y1] -= v
diff[x1][y2+1] -= v diff[x2+1][y2+1] += v

左上角开始产生影响,下方和右方分别取消影响,右下角因为被减了两次,需要再加回来一次。

difference_2d_update.cpp
void addRectangle(int x1, int y1,
                  int x2, int y2, long long value) {
    diff[x1][y1] += value;
    diff[x2 + 1][y1] -= value;
    diff[x1][y2 + 1] -= value;
    diff[x2 + 1][y2 + 1] += value;
}
i
二维差分的还原

所有矩形修改结束后,按照从上到下、从左到右的顺序计算:diff[i][j] += diff[i-1][j] + diff[i][j-1] - diff[i-1][j-1]。此时 diff[i][j] 就是最终矩阵中的值。

TRY IT

给数组做批量区间修改

输入长度为 n 的数组和 m 次操作,每次把区间 [l, r] 加上 k。使用差分输出所有操作完成后的数组,并尝试与逐个修改元素的做法比较运行次数。

基础算法第一阶段完成

现在你已经能够根据数据范围判断复杂度,使用二分缩小查找范围,用前缀和优化区间查询,并用差分优化批量区间修改。

02 · DYNAMIC PROGRAMMING

动态规划学习路线

会从状态定义和转移方程开始,用具体例子解释记忆化、递推顺序与空间优化。

线性 DP背包 DP区间 DP状态压缩
03 · GRAPH THEORY

图论学习路线

将从图的存储和遍历开始,再逐步进入最短路、最小生成树、拓扑排序与连通性问题。

图的存储DFS / BFS最短路最小生成树
04 · NUMBER THEORY

数论学习路线

将介绍整除、最大公约数、质数筛法、快速幂、同余与组合计数等竞赛常用知识。

GCD质数与筛法快速幂同余
COMPETITIVE PROGRAMMING
先认识赛场,再确定方向

常见算法竞赛介绍

不同竞赛在参赛对象、比赛方式和题目难度上各有特点。了解它们不是为了盲目追逐奖项,而是为了给自己的学习找到一个清晰目标。

比赛的本质

在有限时间里,将问题转化为算法,再写成正确、高效的程序。

分析问题设计算法编写代码提交评测
5 类常见赛事

找到适合你的第一场比赛

赛事规则每年可能调整,正式报名和参赛资格请始终以当届官方通知为准。

CHINA COLLEGIATE

中国大学生程序设计竞赛

团队赛

面向中国高校学生的高水平程序设计竞赛,强调算法设计、逻辑推理、编程实现和团队合作。

3 人团队协作实时评测排名系列年度赛事
适合谁已经具备一定算法基础,准备参加高校集训队或团队赛事的同学。
查看 CCPC 官方介绍 ↗
GROUP PROGRAMMING

团体程序设计天梯赛

个人作答

重点考查基础程序设计能力以及数据结构与算法应用能力。选手独立作答,同时通过团体成绩体现学校整体水平。

个人独立答题团体汇总成绩梯度题目分层
适合谁刚开始参加大学算法竞赛,希望检验基础编程能力的同学。
访问天梯赛官方网站 ↗
LAN QIAO CUP

蓝桥杯大赛

个人赛

覆盖软件与电子等多个类别。软件赛中常见 C/C++ 程序设计方向,通常按照不同组别和阶段进行选拔。

个人独立参赛分组对应水平进阶逐级选拔
适合谁希望从个人赛开始积累比赛经验、建立学习目标的高校学生。
访问蓝桥杯官方网站 ↗
BAIDU ASTAR

百度之星程序设计大赛

个人赛

由百度发起的程序设计赛事,重视基础算法、数据结构、编程实现以及分析和解决问题的能力。历史赛事多采用在线评测与逐轮晋级形式。

在线程序评测算法综合挑战个人独立完成
适合谁喜欢挑战算法题、希望通过企业赛事接触不同命题风格的学习者。具体举办信息需关注官方当年公告。
查看百度之星赛事介绍 ↗
深入了解

ICPC 与 CCPC 到底怎样比赛?

这两项赛事是大学算法竞赛中最具代表性的团队赛。下面用一场比赛从开始到结束的过程,把赛制讲清楚。

INTERNATIONAL COLLEGIATE PROGRAMMING CONTEST

国际大学生程序设计竞赛

面向全球高校的多层级团队程序设计竞赛,强调算法能力、临场决策和三人协作。

查看官方规则 ↗
3名队员共同组成一支队伍
1台计算机需要合理安排编码时间
5h经典时长持续分析、编码和调试
20m常见罚时通过题目前的错误提交
01 · 比赛现场

三个人如何使用一台电脑?

三名队员会同时阅读题目,但只有一人能够操作电脑。有人负责推导算法,有人检查边界和样例,有人把已经确认的思路写成代码。角色不是固定职业,而会根据题目和队员特长不断切换。

  • 开场快速读题,判断每题所属算法和预估难度
  • 优先解决把握大的题目,尽快建立提交与排名优势
  • 纸上完成推导和伪代码,减少占用电脑的时间
  • 发现错误后由未编码的队员帮助构造反例
02 · 排名规则

先比解题数,再比总用时

队伍首先按照通过题目数量排名;解题数相同时,总用时更少的队伍靠前。每道通过题的用时从开赛时刻计算到首次通过,之前未通过的提交通常会增加罚时。

示例

A 题在第 40 分钟通过,之前有 2 次错误提交:

40 + 2 × 20 = 80 分钟未解决的题目通常不计入总用时
03 · 晋级与成长

从校内选拔走向区域赛

常见路径是先加入学校集训队,经过校内选拔后代表学校参加相应区域赛事,优秀队伍继续向更高阶段晋级。具体赛区划分、资格和晋级办法应查看当赛季官方规则。

  • 入门:掌握 C++、基础数据结构和常用算法
  • 训练:参加个人赛积累速度,再进行三人组队训练
  • 实战:进行完整 5 小时模拟赛并赛后补题
  • 协作:建立共享模板、读题记录和交叉检查习惯
CHINA COLLEGIATE PROGRAMMING CONTEST

中国大学生程序设计竞赛

面向中国高校学生的年度性高水平赛事,赛题风格和现场形式与 ICPC 团队赛高度相近。

查看官方介绍 ↗
01网络选拔争取全国赛参赛名额
02全国分站赛不同城市巡回举办
03专项赛事女生专场、高职专场等
04年度总决赛优秀高校队伍晋级
01 · 典型赛制

三人一队,实时评测

CCPC 现场赛的典型规则是三名正式队员组成一队,由一名高校教师担任教练。比赛采用上机编程、机器实时评测和实时排名,队伍共同使用一台比赛机器。

总决赛规则示例中,比赛时长为 5 小时,题目通常为英文描述;通过一道题后,赛场会升起对应颜色的气球,这也是现场赛非常有辨识度的传统。

02 · 成绩如何计算

解题数量决定第一顺位

排名首先比较解题数量;数量相同时再比较总用时。每道已通过题目的用时,从比赛开始计算到首次正确提交,之前的错误提交会带来额外罚时。

第一顺位通过题目数量更多
第二顺位总用时与罚时更少
最终结果按当届规则确定奖项
03 · 与 ICPC 的关系

相似赛制,不同赛事体系

两者都非常重视算法、数据结构、代码正确性和团队配合,也都常采用三人一机的现场赛形式。主要区别在于赛事组织体系和覆盖范围:ICPC 是国际赛事体系,CCPC 则重点服务中国高校程序设计竞赛。

备赛知识高度互通,因此高校集训队通常会用同一套训练体系准备两项赛事。

天梯赛

个人完成题目,团队汇总成绩

与三人共用一台电脑不同,选手独立操作和提交。题目通常具有明显梯度,既考查基础语法和读题速度,也逐步覆盖数据结构与算法。它适合用来检验一所学校不同水平选手的整体程序设计能力。

备赛重点:基础题正确率、分段得分、时间分配
蓝桥杯

适合作为个人竞赛的起点

软件类竞赛包含 C/C++ 程序设计等方向,并根据参赛对象设置相应组别。相比团队现场赛,个人赛更直接地检验独立读题、实现和调试能力,常被初学者用作阶段性目标。

备赛重点:语法熟练度、模拟枚举、常用算法
百度之星

接触企业算法赛事的命题风格

历史上的程序设计大赛采用在线评测,重点考查基础算法、数据结构和程序实现能力。它为学习者提供了不同于高校系列赛的题目风格;是否举办、参赛资格和晋级方式需要关注当年官方公告。

备赛重点:综合算法能力、代码速度、线上赛经验
快速对比

我应该先参加哪一个?

赛事主要形式入门友好度建议准备
ICPC / CCPC三人团队赛进阶挑战C++、常用算法、团队配合
天梯赛个人答题、团体计分较友好基础语法、数据结构、读题速度
蓝桥杯个人赛、分组别较友好C++ 基础、模拟、枚举与常用算法
百度之星个人在线赛视赛题而定综合算法能力与代码实现速度
如果你还是零基础

先不用急着选择比赛,从第一行 C++ 代码开始。

进入 C++ 语法学习
MY LEARNING SPACE

你好,学习者

保持好奇,继续向前。你今天完成的每一个小目标,都会成为赛场上的底气。

0%总进度
当前学习阶段C++ 基础入门

完成第一章,开启你的算法之旅

LEARNING OVERVIEW

学习概览

今天也要保持思考
C++
已完成章节0/ 14 章
AC
已完成题目0/ 32 题
DAY
连续学习1

每一次回来,都算进步

PTS
刷题积分0

完成更多题目,冲击排行榜

01
为你推荐的下一步

从第一章开始学习

循序渐进地掌握 C++,为算法学习打好基础。

开始学习
YOUR ROADMAP

成长路径

01
C++ 语法基础正在进行
02
基础算法下一阶段
03
专题训练持续解锁
04
参加竞赛最终目标
ALGOSPARK RANKING

学习排行榜

榜单按照学习积分排序,同分时完成题目更多的学习者优先。

积分规则洛谷训练题 · 手动完成得 3—5 分协会 OJ · 首次 AC 后计分同一道题 · 不重复计分
TOP 50

积分榜

排名学习者姓名学校完成题数积分
正在加载排行榜…

排行榜展示注册时填写的昵称与姓名,同时统计洛谷手动完成积分和协会 OJ 判题积分。

ACCESS CHECK

正在验证访问权限

请稍候,系统正在确认你的管理员身份。

返回首页
PROBLEM STATEMENT

在线作答

正在读取协会 OJ 题面…
CODE & JUDGE

编写并提交代码

代码将使用你绑定的 Hydro 身份进行评测

Tab 缩进 · Ctrl + Enter 提交
提交前请先阅读题目并检查输入输出格式。
已复制到剪贴板