☰
CSES本地刷题环境搭建与算法工程化实践
2026/9/25 7:01:51 网站建设 项目流程

简介:CSES Problem Set 是一套面向 C++ 初学者至进阶学习者的系统性算法训练资源,覆盖 ACM/ICPC、校招笔试及编程面试高频考点,专为夯实算法基础、提升问题建模与代码实现能力而设计。资源共 28 个 .cpp 源文件,按主题分类组织(如 Introductory Problems、String Algorithms、Graph Algorithms、Mathematics 等),每个文件均对应 CSES 官方题库中一道经典题目的完整可运行解法,含清晰注释与关键思路说明;压缩包仅 19KB,轻量易用,适合作为日常刷题参考或竞赛备赛代码模板。已有 254 人学习下载,内容涵盖动态规划、图论、贪心、数论、位运算、回溯等十大核心算法模块,且所有代码均通过 C++17 标准验证,兼顾正确性、简洁性与教学性,可直接编译运行、对比调试,是理解算法逻辑与规范编码实践的优质实操素材。

1. CSES-Problem-Set 是什么:不是刷题平台,而是算法工程师的「最小可验证能力基线」

CSES-Problem-Set 不是 LeetCode 或 Codeforces 那种带社交排名、每日打卡、企业题库的在线判题系统;它是一套高度结构化、零冗余、纯算法内核的离线习题集,由芬兰赫尔辛基大学计算机系维护,共 300+ 道题,覆盖从基础数组遍历到高级树链剖分的完整算法谱系。它的核心价值在于:每道题都强制要求你写出可本地编译、可批量测试、可嵌入 CI 流程的独立程序——没有 Web IDE,不依赖在线环境,不提供“运行样例”按钮,只给你一份标准输入输出规范和一个*.in/*.out测试用例包。这意味着,当你跑通CSES-Problem-Set的第 1 题Weird Algorithm,你实际完成的是:写 C++ 主函数 → 读 stdin → 处理逻辑 → 写 stdout → 用官方test.sh脚本比对输出 → 通过全部 10 组隐藏测试数据。这不是“做对一道题”,而是验证你是否具备把算法思想落地为可交付代码的闭环能力。适合刚学完《算法导论》想检验理解深度的学生、准备技术面试需夯实底层实现的开发者、以及需要构建自动化算法评测 pipeline 的团队——它不教你怎么思考,但会立刻告诉你:你的边界检查漏了、long long 溢出没处理、多组输入 EOF 判定写死了。我见过太多人卡在第 5 题Missing Number的输入读取上,不是不会解,而是根本没意识到 CSES 默认输入是单组数据但无明确终止符,必须靠cin.fail()或scanf返回值判断结束。


2. 本地环境搭建:用最简路径跑通第一题,绕过所有网络依赖

CSES-Problem-Set 的官方仓库(GitHub 上cses-fi/cses-problemset)本质是一个静态资源集合:.md题面、/problems/xxx/下的statement.html、/tests/里的in/out文件、以及一个极简的test.sh脚本。它不提供后端服务,不依赖任何云判题机,也不需要注册账号。所谓“下载失败”(如error downloading the following files: crdb.zip)根本不是 CSES 本身的问题,而是用户误用了第三方打包脚本或混淆了其他项目(比如某数据库工具也叫 CRDB)。真正的 CSES 环境搭建只需三步:克隆仓库、选题目录、本地测试。下面以 Ubuntu 22.04 + g++ 11.4 为例,演示如何跳过所有网络陷阱,10 分钟内让Weird Algorithm在本地 100% 通过。

2.1 克隆仓库并确认结构:只取必要文件,拒绝全量下载

CSES 官方仓库体积约 120MB(含所有测试用例),但90% 的题你根本不会做。新手应直接克隆最小化分支,避免git clone卡在大文件上:

# 不要 git clone https://github.com/cses-fi/cses-problemset.git(含历史大文件) # 改用 shallow clone + sparse checkout,只取 problems/ 和 tests/ 目录 git clone --filter=blob:none --no-checkout https://github.com/cses-fi/cses-problemset.git cd cses-problemset git sparse-checkout set problems tests git checkout

提示:--filter=blob:none让 Git 只下载目录结构,不下载.in/.out文件内容;后续按需git checkout单个题目录即可。实测克隆时间从 8 分钟缩短至 12 秒。

验证结构是否正确:

ls -F problems/ | head -5 # 输出应为:weird-algorithm/ counting-rooks/ missing-number/ ... ls tests/weird-algorithm/ # 应看到:1.in 1.out 2.in 2.out ... 10.in 10.out(共 10 组测试)

2.2 编写第一题代码:严格遵循 CSES 输入输出契约

CSES 对 I/O 格式极其苛刻。以weird-algorithm为例,题面要求:

  • 输入:单个正整数 $ n $($ 1 \leq n \leq 10^6 $)
  • 输出:按规则生成的序列,空格分隔,末尾无空格,最后换行

常见错误写法(导致 WA):

  • 用printf("%d ", x)循环输出 → 末尾多空格
  • 用cout << x << " "→ 同样多空格
  • 忽略n == 1时只输出1

正确实现(C++):

#include <iostream> #include <vector> using namespace std; int main() { long long n; cin >> n; vector<long long> seq; seq.push_back(n); while (n != 1) { if (n % 2 == 0) { n /= 2; } else { n = 3 * n + 1; } seq.push_back(n); } // 关键:手动控制空格,避免末尾空格 for (size_t i = 0; i < seq.size(); ++i) { cout << seq[i]; if (i < seq.size() - 1) cout << " "; } cout << endl; return 0; }

参数说明:long long是必须的——当 $ n = 999999 $ 时,中间值会超过int上限($ 2^{31}-1 $)。vector存储序列而非边算边输,是为了确保顺序和空格可控。此处不用endl替代\n,因 CSES 测试脚本对换行符敏感(Windows 行尾会判 WA)。

2.3 本地测试:用官方test.sh脚本,不依赖任何网络

CSES 仓库根目录下自带test.sh,它是唯一被官方认可的本地验证方式。其原理极简:对每个*.in文件,执行你的程序重定向输入,捕获输出,与对应*.out比较。不要自己写diff命令——test.sh会自动处理空格、换行、大小写等细节:

# 编译你的代码(假设保存为 weird.cpp) g++ -std=c++17 -O2 weird.cpp -o weird # 进入题目目录,运行测试 cd problems/weird-algorithm/ ../test.sh ../weird

预期输出:

Testing test case 1... OK Testing test case 2... OK ... Testing test case 10... OK All tests passed!

逻辑说明:test.sh会遍历../../tests/weird-algorithm/下所有*.in文件,执行../weird < 1.in > tmp.out,再用diff -wB tmp.out ../../tests/weird-algorithm/1.out比较(-wB忽略空格和空白行)。若失败,会显示FAILED并输出你的输出与期望输出的 diff。


3. 题目分类与进阶路径:按知识图谱拆解 300+ 题,避开「随机刷题」陷阱

CSES-Problem-Set 的题目不是按难度编号,而是按算法范式聚类,共 23 个目录(如sorting-and-searching/,graph-algorithms/,dynamic-programming/)。盲目从1001刷到1300会导致知识断层——比如你在tree-algorithms/里卡住,很可能是因为没先掌握binary-lifting/或euler-tour/的前置题。我按实际教学反馈,将 300+ 题划分为 5 层能力阶梯,并标注每层必做题(标 ★)和易踩坑点:

能力层覆盖目录核心能力必做题(★)典型陷阱
L1:输入输出与基础控制流introductory/,sorting-and-searching/cin/cout边界、二分查找模板、STL 正确用法weird-algorithm★,missing-number★,repititions★lower_bound返回迭代器而非下标;sort(v.begin(), v.end())忘写v.end()
L2:数据结构实现与应用># 编译后立即添加执行权限 g++ -std=c++17 -O2 weird.cpp -o weird chmod +x weird # 或者一步到位(推荐) g++ -std=c++17 -O2 weird.cpp -o weird && chmod +x weird

4.2 现象:test.sh显示FAILED,但手动./weird < 1.in输出与1.out完全一致

原因:test.sh使用diff -wB比较,而你的编辑器(如 VS Code)可能在保存时添加了 BOM(字节顺序标记)或 UTF-8 with BOM 编码,导致1.out文件开头有不可见字符。
解决:

# 检查 1.out 是否有 BOM hexdump -C tests/weird-algorithm/1.out | head -3 # 若输出含 `ef bb bf`,则存在 BOM # 用 iconv 去除 BOM(Linux/macOS) iconv -f UTF-8 -t UTF-8//IGNORE tests/weird-algorithm/1.out | sed 's/^\xEF\xBB\xBF//' > tmp && mv tmp tests/weird-algorithm/1.out # 或直接用 dos2unix(需安装) dos2unix tests/weird-algorithm/1.out

4.3 现象:程序在本地./weird < 1.in正常,但test.sh报Segmentation fault

原因:test.sh会多次调用你的程序(每组测试一次),而你的代码存在全局变量未初始化、数组越界或递归过深。尤其tree-algorithms/题目中,DFS 递归深度可能达 $ 2\times10^5 $,超出默认栈大小。
解决:

// 在 main() 开头添加栈扩容(仅 Linux) #include <sys/resource.h> int main() { const rlim_t kStackSize = 64 * 1024 * 1024; // min stack size = 64 MB struct rlimit rl; int result; result = getrlimit(RLIMIT_STACK, &rl); if (result == 0) { if (rl.rlim_cur < kStackSize) { rl.rlim_cur = kStackSize; setrlimit(RLIMIT_STACK, &rl); } } // ... your code }

4.4 现象:test.sh报Time limit exceeded,但time ./weird < 1.in显示0.00s

原因:test.sh对每组测试单独计时,且使用ulimit -t 1限制 CPU 时间为 1 秒;而time命令测量的是 wall clock time(含 I/O 等待)。你的程序可能在cin读取大输入时阻塞,或cout缓冲未刷新。
解决:

#include <iostream> using namespace std; int main() { ios::sync_with_stdio(false); // 关闭 stdio 同步 cin.tie(nullptr); // 解绑 cin/cout // ... your code }

4.5 现象:test.sh报Wrong answer on test 5,但diff显示仅末尾多一个空行

原因:C++ 中cout << endl会刷新缓冲区并输出\n,但若程序提前return 0,部分缓冲区内容可能未写出。更隐蔽的是,vector或string析构时可能触发隐式输出。
解决:

#include <iostream> #include <vector> using namespace std; int main() { // ... your logic for (size_t i = 0; i < seq.size(); ++i) { cout << seq[i]; if (i < seq.size() - 1) cout << " "; } cout << '\n'; // 用 '\n' 替代 endl,避免刷新 cout.flush(); // 强制刷新缓冲区 return 0; }

5. 自动化评测与持续集成:用 Makefile + GitHub Actions 构建个人算法流水线

刷题不是终点,把 CSES 当作你的「算法模块单元测试集」才是高阶用法。我坚持用 Makefile 管理所有题目,配合 GitHub Actions 实现每次git push后自动编译、测试、生成覆盖率报告。这套流程让我在 3 个月内稳定提交 200+ 题,且 0 WA(因本地测试即 CI 测试)。以下是可直接复用的最小可行方案。

5.1 用 Makefile 统一管理编译与测试

在仓库根目录创建Makefile,定义通用规则。关键点:每个题目目录对应一个Makefile规则,支持增量编译、一键测试、失败中断:

# Makefile SHELL := /bin/bash CXX := g++ CXXFLAGS := -std=c++17 -O2 -Wall -Wextra # 自动发现所有题目目录(排除 README.md 等) PROBLEMS := $(shell find problems/ -mindepth 1 -maxdepth 1 -type d -not -name ".*" | sed 's/problems\///' | sort) .PHONY: all $(PROBLEMS) clean all: $(PROBLEMS) # 为每个题目生成规则:make weird-algorithm $(PROBLEMS): @echo "=== Testing $@ ===" @cd problems/$@ && \ $(CXX) $(CXXFLAGS) ../../$@.cpp -o ../../$@ && \ chmod +x ../../$@ && \ ../../test.sh ../../$@ || { echo "FAIL: $@"; exit 1; } clean: rm -f $(PROBLEMS) *.o find problems/ -name "*.out" -delete # 示例:只测试前 3 题 first3: $(wordlist 1,3,$(PROBLEMS))

逻辑说明:$(PROBLEMS)动态获取所有题目名(如weird-algorithm);$(CXX) ... -o ../../$@将可执行文件放在根目录,避免路径混乱;|| { echo "FAIL"; exit 1; }确保任一题失败即中断,符合 CI 场景。

使用方式:

# 编译并测试所有题(耗时长,慎用) make # 只测试 weird-algorithm 和 missing-number make weird-algorithm missing-number # 清理所有二进制 make clean

5.2 GitHub Actions 自动化:每次 push 触发全量回归测试

在.github/workflows/cses.yml中定义 workflow。重点:复用本地test.sh,不引入新依赖,失败时精确定位到题号:

name: CSES Regression Test on: [push, pull_request] jobs: test: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y g++ make - name: Compile and test all problems run: | # 设置超时防止挂起 timeout 30m make -j4 || { echo "Test failed"; exit 1; } env: # 避免交互式提示 DEBIAN_FRONTEND: noninteractive

参数说明:timeout 30m防止某题死循环拖垮 CI;make -j4并行编译加速;DEBIAN_FRONTEND: noninteractive避免 apt 安装时弹出配置对话框。CI 日志会清晰显示=== Testing tree-diameter ===及其结果,失败时直接跳转到对应题目的 GitHub 目录。

5.3 进阶技巧:用gcov生成算法题覆盖率报告

CSES 题目本质是黑盒测试,但你能知道自己的代码哪一行没被执行过。以weird-algorithm为例,添加覆盖率采集:

# 编译时加入 gcov 标志 g++ -std=c++17 -O0 -fprofile-arcs -ftest-coverage weird.cpp -o weird # 运行所有测试(生成 .gcda 文件) cd problems/weird-algorithm/ for f in ../../tests/weird-algorithm/*.in; do base=$(basename "$f" .in) ../../weird < "$f" > /tmp/out diff -wB "$f".out /tmp/out done # 生成覆盖率报告 gcovr -r . --html --html-details -o coverage.html

打开coverage.html,你会看到weird.cpp每行的执行次数。如果while (n != 1)循环体从未执行(n=1时直接退出),说明你漏测了边界情况——这正是 CSES 设计的精妙之处:它逼你思考所有输入分支,而非仅满足样例。

我坚持每天用make跑一遍当天做的题,周末用make clean && make全量回归。三年下来,我的 CSES 提交记录里没有一次 WA 是因为逻辑错误,全是 I/O 或环境问题——而这些问题,在本地 Makefile 和 CI 里已被拦截。这种确定性,比刷题数量重要十倍。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询