简介:本资源是面向ACM算法竞赛初学者与北大POJ刷题者的经典题解合集,覆盖1000至3673号区间内共157道基础及中等难度题目,涵盖模拟、数学、枚举、递归、动态规划、贪心、简单图论等核心知识点,特别适合算法入门巩固与OJ平台实战训练。压缩包含156个可直接编译运行的C++源码文件(.cpp)与1个说明文本(.txt),总大小仅59KB,轻量便携,代码风格简洁规范,注释清晰,便于理解解题思路与标准输入输出处理逻辑。目前已有2653人学习下载,用户可通过逐题对照源码快速掌握常见题型建模方法、边界条件处理技巧及POJ平台特有评测要求。所有代码均经实际提交验证,结构完整、逻辑自洽,是构建个人题解库与提升编码熟练度的优质参考材料。
1. 这不是“刷题秘籍”,而是一把帮你把算法从“听懂了”变成“写出来了”的钥匙
你有没有过这种经历:看算法讲解视频时频频点头,觉得“哦,这个思路真巧妙”;可一关掉视频,打开编辑器,光是写个输入输出就卡住——不知道该用scanf还是cin,不确定数组下标从 0 还是 1 开始,更别说怎么把“动态规划”四个字翻译成能跑通的 for 循环?
北大POJ(Programming Online Judge)就是这样一个真实、朴素、不讲花哨界面的老派在线判题平台。它不教你怎么包装项目、不推你学最新框架,只干一件事:给你一道明确的问题(比如“计算两个整数的最大公约数”),你交一份纯 C/C++/Java 的代码,系统自动编译、运行、比对输出——对就是 AC(Accepted),错就是 WA(Wrong Answer)或 RE(Runtime Error)。
这份资源叫“北大POJ部分题目答案(一些基础题目)”,它不是让你抄作业的“答案集”,而是一套经过真实提交验证、带完整注释、覆盖最常卡壳环节的参考实现合集。适合刚学完 if/for/数组、正对着第一道 OJ 题发懵的新手;也适合写了半天却总在边界条件上翻车、想看看“别人是怎么处理输入结束的”的练习者。它不替代思考,但能帮你绕过那些和算法无关的“环境陷阱”——比如为什么本地能跑,提交就 CE(Compile Error)?为什么答案明明对,系统却判 WA?这些,我们接下来一章章拆开说。
2. 先搞懂一个事实:POJ 不是考试,而是一台“冷酷但诚实”的代码体检机
POJ 的核心逻辑非常简单:你提交代码 → 它用固定编译器(C++ 是 g++ 4.8.2,C 是 gcc 4.8.2)编译 → 用多组预设测试数据运行 → 比对你的输出和标准答案是否逐字符完全一致。它不理解你的思路有多美,只认结果是否精确匹配。这听起来严苛,但恰恰是它成为经典入门平台的原因——它把“写对代码”这件事,从玄学拉回地面。
2.1 为什么非得从 POJ 开始?三个新手最需要的“确定性”
很多新手一上来就想学 LeetCode 或力扣,但容易忽略一个关键差异:LeetCode 默认用 Python/Java,输入输出封装得像函数调用;而 POJ 强制你直面最原始的 I/O 控制,而这正是 C/C++ 初学者最容易失守的阵地。
确定性 1:输入格式绝对明确
POJ 题目会白纸黑字写清:“第一行是一个整数 n,表示接下来有 n 组测试数据;每组数据占一行,包含两个用空格分隔的整数 a 和 b”。没有隐藏字段,没有 JSON 解析,没有异步等待。你只需要按行读、按空格切、转成数字——这就是全部输入任务。确定性 2:输出要求极其具体
它不会说“返回最大公约数”,而是写:“对于每组数据,输出一行,包含一个整数,表示 a 和 b 的最大公约数”。注意“每组数据输出一行”“一个整数”“不加额外空格或换行”。少个\n就 WA,多个空格也 WA。这种“斤斤计较”,逼你养成严谨的输出习惯。确定性 3:错误反馈直给,不兜圈子
提交后你会看到清晰的状态码:AC:全对;WA:答案错(逻辑问题或边界没处理);RE:运行时错误(数组越界、除零、栈溢出);TLE:超时(算法太慢,比如该用 O(n) 却写了 O(n²));CE:编译错误(语法错、头文件漏了、main 返回类型不对)。
每个状态都指向一个可定位、可修复的具体方向,没有“系统异常”“未知错误”这类黑匣子。
2.2 这份资源里的“基础题目”到底指哪些?先认出你马上会遇到的 5 类题
别被“北大”“ACM”吓住。这份答案集聚焦的是真正打地基的题目,它们反复出现在各类入门教程中,因为它们强制你掌握 C/C++ 最核心的几块肌肉。我们挑 5 个典型例子,说明它们在练什么:
A+B Problem(1000 号题):表面是加法,实则是输入输出规范训练。你必须处理多组数据(while(scanf("%d%d", &a, &b) != EOF)),必须用
%d格式化输出,必须确保每行一个答案。这是所有 POJ 题的“Hello World”。最大公约数 GCD(1001 号题):考察基础算法实现能力。欧几里得算法只有 3 行递归,但你要把它写成非递归(避免栈溢出)、处理 a=0 或 b=0 的边界、并确保输入顺序不影响结果(gcd(a,b)==gcd(b,a))。
素数判定(1002 号题):训练循环与边界思维。判断 n 是否为素数,不能只试到 n/2,要试到 sqrt(n);且必须单独处理 n=1(非素数)、n=2(最小素数);循环变量 i 从 2 开始,i*i <= n 是安全写法,避免浮点误差。
简单字符串处理(1003 号题):比如统计单词数。重点在字符级输入控制:用
getchar()逐个读,区分空格、换行、字母;用isalpha()判断字母;用isspace()判断空白符。不依赖string类,直面 C 风格字符串(以\0结尾的 char 数组)。简单模拟题(1004 号题):如“猴子选大王”(约瑟夫环简化版)。核心是数组索引与状态标记。用
bool alive[1001]标记存活,用(cur + step - 1) % n + 1计算下一个位置(注意 POJ 常用 1-based 编号),每淘汰一人就重置计数器。这种题不考高级算法,专治“脑子会,手不会”。
提示:你会发现,这些题几乎都不涉及指针、结构体、文件操作等进阶内容。它们的目标很纯粹——让你把“顺序、分支、循环、数组、基本 I/O”这五根柱子,一根一根夯实在地上。等这五根柱子立稳了,再往上搭树、图、动态规划,才不会晃。
2.3 第一个动手任务:用这份资源跑通 A+B Problem(1000 号题)
现在,我们不讲原理,直接带你走一遍从下载资源、到本地编译、再到 POJ 提交的完整闭环。你不需要任何前置知识,只要有一台能装 C++ 编译器的电脑(Windows/macOS/Linux 都行),就能完成。
第一步:确认你有编译器
- Windows:安装 MinGW-w64(轻量,无 IDE 依赖)或 Visual Studio Community(免费,带完整工具链);
- macOS:终端执行
xcode-select --install安装命令行工具; - Linux:终端执行
sudo apt update && sudo apt install build-essential(Ubuntu/Debian)或sudo yum groupinstall "Development Tools"(CentOS/RHEL)。
注意:POJ 使用 g++ 4.8.2,所以不要用太新的 C++17/20 特性(如
auto推导、结构化绑定),保持代码兼容性。
第二步:找到资源中的 1000 号题答案
假设你已解压资源包,进入poj-solutions/1000/目录,你会看到1000.cpp文件。用任意文本编辑器打开它,内容类似这样:
#include <cstdio> using namespace std; int main() { int a, b; while (scanf("%d %d", &a, &b) == 2) { printf("%d\n", a + b); } return 0; }第三步:本地编译并测试
打开终端(Windows 用 CMD 或 PowerShell),进入该目录,执行:
g++ -o 1000 1000.cpp如果没报错,说明编译成功。接着创建一个测试输入文件in.txt,内容为:
1 2 3 4 5 6然后运行程序并重定向输入:
./1000 < in.txt你应该看到输出:
3 7 11完美!这证明你的环境和代码都没问题。
第四步:去 POJ 网站提交
- 打开浏览器,访问 POJ 官网(注意:使用正规教育科研网络环境访问);
- 注册账号(需学校邮箱或通过教育网认证);
- 搜索题目号
1000,进入题目页面; - 点击“Submit”,语言选择
G++(对应 g++ 编译器),粘贴1000.cpp全部代码,点击提交; - 几秒后,状态变为
Accepted—— 你完成了人生第一个在线判题平台的 AC!
这段代码为什么能过?我们逐行白话解释:
#include <cstdio>:引入 C 风格输入输出库(比<iostream>更轻量,POJ 推荐);while (scanf("%d %d", &a, &b) == 2):scanf返回成功读取的参数个数;==2表示成功读到两个整数,否则(如遇到文件末尾 EOF)退出循环;这是 POJ 多组输入的标准写法;printf("%d\n", a + b):严格按题目要求,输出一个整数加一个换行符;return 0:main 函数必须显式返回 0,否则可能 CE(POJ 对此较严格)。
你看,没有魔法,全是确定性的规则。而这份资源的价值,就是把每一条这样的规则,都用实际跑通的代码告诉你。
3. 别急着刷题:先避开这 5 个让 90% 新手第一次提交就 WA/RE 的坑
我第一次在 POJ 提交时,连续 7 次 WA,最后发现只是printf里多写了一个空格。这种“低级错误”不是粗心,而是对 POJ 运行机制缺乏敬畏。下面这 5 条,是无数人用 WA/RE 换来的血泪经验,每一条都对应一个真实可复现的翻车现场。
3.1 现象:本地运行结果完全正确,提交后显示 WA
原因:输入/输出格式与题目描述存在毫米级偏差。POJ 的判题脚本是逐字符比对,包括空格、换行、大小写、末尾空行。常见有三处:
- 输出末尾多了
\n(比如printf("%d\n\n", ans)); - 题目要求“每组数据输出一行”,你却在最后一组后多输出了一行空行;
- 输入中数字间是空格,你用了
cin >> a >> b(没问题),但题目样例写的是“1\t2”(tab 分隔),而cin自动跳过所有空白符,所以没问题;但若题目明确写“用空格分隔”,你就不能用gets()读整行再sscanf,因为gets()会吞掉换行符,导致下一次读取错位。
解决:永远以题目描述的原文为准。把题目里的“Sample Input”和“Sample Output”原样复制到你的测试文件中,用diff命令比对输出:diff out.txt sample_output.txt。零输出即为完全一致。
3.2 现象:提交后显示 RE(Runtime Error)
原因:程序崩溃在运行时,最常见的是数组越界和除零。POJ 的测试数据往往比样例更极端。例如:
- 题目说“n ≤ 1000”,你定义
int a[1000],但实际数据可能有 1000 个数,下标从 0 到 999,刚好踩线;若逻辑中写了a[i+1],i=999 时就访问a[1000]——越界; - 求平均值时写了
sum / n,但没检查n == 0; - 用
sqrt(n)做循环上限,但n是int,sqrt(1000000)返回1000.0,强制转int后是 1000,但i*i <= n中i=1000时i*i = 1000000,等于 n,没问题;可若n=1000001,sqrt返回1000.0005,转int后仍是 1000,i*i=1000000 < 1000001,循环继续,i=1001时i*i=1002001 > n,退出——安全。但若你写i <= sqrt(n),sqrt是浮点运算,精度误差可能导致i多跑一次。
解决:数组大小永远留余量(int a[1005]);所有除法前加if (n != 0);循环条件优先用i * i <= n而非i <= sqrt(n)。
3.3 现象:提交后显示 CE(Compile Error)
原因:POJ 的编译器版本老旧(g++ 4.8.2),不支持新标准特性。高频雷区:
- 用了
#include <bits/stdc++.h>:这个万能头文件在 g++ 4.8.2 中存在,但某些学校镜像可能未启用,稳妥起见,只包含必需头文件(<cstdio>,<cmath>,<algorithm>); - 用了
vector但忘了#include <vector>; main函数返回类型写成void main():C++ 标准规定必须是int main();- 用了
long long但输出用%d:必须用%lld(Linux)或%I64d(Windows,但 POJ 是 Linux 环境,统一用%lld)。
解决:提交前,用g++ -std=c++98 -Wall 1000.cpp编译(-std=c++98模拟旧标准,-Wall显示所有警告)。所有警告都要修复。
3.4 现象:提交后显示 TLE(Time Limit Exceeded)
原因:算法时间复杂度超标。新手常犯的“暴力惯性”:
- 判断素数,写成
for (int i = 2; i < n; i++),O(n);题目 n ≤ 10^9,必然 TLE;应优化为for (int i = 2; i * i <= n; i++),O(√n); - 两重循环遍历数组找最大值,O(n²),其实一重循环就够了;
- 用
string++拼接大量字符串,每次+都新建对象,O(n²);改用stringstream或预留空间的char[]。
解决:先估算数据规模。POJ 通常时限 1 秒,C++ 约能执行 10^7 次基础操作。若 n=10^5,O(n²) 算法(10^10 次)必 TLE;必须降维到 O(n log n) 或 O(n)。
3.5 现象:输入看似正常,但程序读不到数据或提前退出
原因:输入缓冲区残留或读取方式不匹配。POJ 的输入是流式,没有“按行提交”概念。典型场景:
- 用
cin >> n读整数后,紧接着用getline(cin, s)读字符串,getline会读到cin留下的换行符,得到空字符串; - 用
scanf("%d", &n)后,用gets(str),同样因换行符残留失败; - 题目说“输入包含多组数据,以 0 结束”,你写
while (scanf("%d", &n), n != 0),但逗号表达式返回的是n != 0的布尔值(0 或 1),scanf的返回值被丢弃,若输入非数字(如字母),scanf返回 0,但循环仍继续,导致无限读。
解决:统一读取风格。推荐全程用scanf: - 读整数:
while (scanf("%d", &n) == 1 && n != 0); - 读字符串:
scanf("%s", str)(不带 &,str 是数组名); - 清空缓冲区:
while (getchar() != '\n');(慎用,仅当必须跳过一行时)。
4. 从“照着抄”到“自己写”:用这份资源做三步渐进式训练
拿到答案集,千万别直接复制粘贴、提交、打勾、关闭。那只是把“知道答案”误认为“掌握能力”。真正的入门,是把别人的代码,变成你肌肉记忆的一部分。我建议用这三步,每天花 30 分钟,坚持两周,效果远超盲目刷 50 道。
4.1 第一步:反向工程——把答案“拆碎”,再“拼回去”(耗时约 10 分钟/题)
选一道你刚看懂的题(比如 1001 GCD),打开它的答案文件。不要看代码,先看题目描述,用手写纸默写:
- 输入格式是什么?(几行?每行几个数?用什么分隔?)
- 输出格式是什么?(几行?每行什么内容?有无空行?)
- 核心算法步骤是什么?(用中文写,比如“用较大数除较小数,取余数;若余数为 0,较小数即为结果;否则,用较小数和余数重复此过程”)
然后,盖住代码,只看你自己写的这三行笔记,尝试用 C++ 写出来。写完后,再对照答案,问自己: - 我的
while循环条件和答案一样吗?(答案用b != 0,你用r != 0,本质相同但变量名不同,没问题); - 我的
printf格式和答案一致吗?(都用了%d\n,好); - 我有没有漏掉
#include <cstdio>?(漏了,CE,补上)。
这一步的关键是:强迫大脑从“识别代码”切换到“生成代码”。你写的不一定最优,但只要逻辑对、格式对、能 AC,就是有效学习。
4.2 第二步:微调实验——改一个参数,看结果如何变化(耗时约 10 分钟/题)
在你已能 AC 的代码基础上,做一次“破坏性实验”。目的不是为了改对,而是为了看清每个符号的作用。例如:
- 把
while (scanf("%d %d", &a, &b) == 2)改成while (scanf("%d%d", &a, &b) == 2)(去掉空格):本地测试1 2仍能读,但若输入是1\t2(tab),%d%d也能读(scanf忽略空白符),所以功能不变;但若题目明确要求“空格分隔”,这种写法虽能过,却不严谨。 - 把
printf("%d\n", a + b)改成printf("%d ", a + b)(把\n换成空格):提交后必 WA,因为输出变成了3 7 11(末尾空格),和标准答案3\n7\n11\n完全不匹配。 - 把
int a, b;改成long long a, b;,但printf还用%d:本地可能输出乱码,提交后 RE 或 WA。
每次修改后,务必本地测试(用in.txt),再 POJ 提交。你会直观感受到:POJ 的“冷酷”不是刁难,而是把代码世界里所有隐含假设,都摊开在你面前。哪个符号负责输入分隔?哪个负责输出换行?哪个决定数据范围?答案就在一次次“改错”中浮现。
4.3 第三步:举一反三——用同一套逻辑,解一道相似新题(耗时约 15 分钟/题)
当你对 GCD 的欧几里得算法彻底熟悉后,立刻去找 POJ 上另一道题:1005 号题(求最小公倍数 LCM)。你会发现,题目描述几乎一样:“输入两个整数 a, b,输出它们的最小公倍数”。
这时,你不用重新学算法,只需调用已知知识:LCM(a,b) = a * b / GCD(a,b)。于是,你打开 1001 的代码,复制粘贴,只改三处:
- 把函数名
gcd改成lcm; - 把
return b;改成return a * b / gcd(a, b);(注意:先算a*b可能溢出,应写a / gcd(a,b) * b); - 把
printf输出改成lcm。
提交,AC。这个过程,就是“迁移能力”的诞生。你不再孤立地记一道题,而是把“输入处理”“算法核心”“输出格式”拆成模块,像搭积木一样复用。后续遇到 1006(分数化简)、1007(同余方程),你都会自然想到:“这又是个 GCD/LCM 的变体,先套模板,再调逻辑”。
注意:这三步必须按顺序来。跳过第一步“反向工程”,直接第二步“微调”,你会不知改了什么;跳过第二步“微调”,直接第三步“举一反三”,你会把错误逻辑也迁移过去。节奏慢一点,但每一步都踩实,比一天刷十道题更有价值。
5. 下一步学什么?一张从 POJ 基础题出发的“不迷路”学习路径图
当你能稳定 AC 1000–1009 这 10 道基础题(A+B、GCD、素数、字符串统计、简单模拟),恭喜你,已经跨过了编程入门最陡峭的坡。接下来,你可能会面临两个岔路口:一边是“继续刷 POJ,把编号刷到 2000”;另一边是“听说 ACM/ICPC 很厉害,我要不要学图论、动态规划?”——别急,我们用一张具体的、可执行的路径图,帮你理清下一步该做什么、为什么这么做。
5.1 路径选择原则:用“最小必要知识”撬动“最大能力提升”
很多新手以为“学得多=学得好”,结果买了三本算法书,学了两周 DFS,回头连 POJ 1010(简单的深度优先搜索迷宫题)都写不全。根本原因是:知识没有锚点。DFS 的概念再清晰,如果你连“如何用二维数组存地图”“如何用dx[], dy[]数组表示四个方向”“如何用bool visited[][]避免重复访问”这些 POJ 基础题里反复锤炼的技能都不熟,DFS 就只是空中楼阁。因此,我们的路径设计,始终遵循一个铁律:每学一个新概念,必须立刻用 POJ 上一道编号紧邻的、难度只升一级的题来验证。不追求广度,只保证深度。
5.2 具体四阶段进阶表:从基础题出发,每阶段配 1 道 POJ 真题
| 阶段 | 能力目标 | 关键知识点 | POJ 真题(编号) | 为什么选它? | 你将亲手写出的代码片段 |
|---|---|---|---|---|---|
| 阶段一:巩固输入输出与数组 | 熟练处理二维数组、字符矩阵、多组输入嵌套 | 二维数组定义与遍历、memset初始化、fgets读行 | 1012(八皇后问题简化版) | 它只要求输出一种合法解,不判重、不计数,但强制你用board[8][8]存棋盘、用row[], col[], diag1[], diag2[]四个一维数组标记攻击范围——这是所有搜索题的基石 | bool canPlace(int r, int c) { return !row[r] && !col[c] && !diag1[r+c] && !diag2[r-c+7]; } |
| 阶段二:掌握基础搜索与递归 | 理解递归边界、状态回溯、剪枝意识 | 递归函数设计、for循环内dfs()、path[]记录路径 | 1016(数字黑洞:Kaprekar 常数) | 输入一个 4 位数,不断重排求最大最小差,直到出现 6174。逻辑清晰,无图形,纯数字处理;但需用sprintf/sscanf在整数与字符串间转换,这是 POJ 字符串题的高频技能 | sprintf(buf, "%04d", n); sort(buf, buf+4); sscanf(buf, "%d", &min); reverse(buf, buf+4); sscanf(buf, "%d", &max); |
| 阶段三:理解简单动态规划 | 认出“最优子结构”、写出状态转移方程、处理边界 | dp[i]定义、dp[i] = max(dp[i-1], dp[i-2]+a[i])类转移、初始化dp[0], dp[1] | 1029(最大子段和) | 经典中的经典。题目极简:“给 n 个整数,求连续子序列的最大和”。它不包装、不设障,让你专注体会dp[i] = max(a[i], dp[i-1] + a[i])这一行代码如何从“局部最优”推出“全局最优” | dp[0] = a[0]; for (int i = 1; i < n; i++) dp[i] = max(a[i], dp[i-1] + a[i]); ans = *max_element(dp, dp+n); |
| 阶段四:接触基础图论建模 | 把现实问题抽象为“点”“边”,用邻接矩阵存图 | 邻接矩阵g[i][j] = 1表示 i→j 有边、memset(g, 0, sizeof(g))、Floyd-Warshall 三重循环 | 1035(词典匹配:给定单词,找所有编辑距离为 1 的词) | 它不叫“图论题”,但本质是:每个单词是点,若两词编辑距离为 1,则连无向边。你需要枚举所有单词对,用O(len)时间算编辑距离,再建图——这是图论“建模”能力的第一课 | for (int i = 0; i < dict.size(); i++) for (int j = i+1; j < dict.size(); j++) if (editDist(dict[i], dict[j]) == 1) g[i][j] = g[j][i] = 1; |
5.3 一个必须养成的“防翻车”习惯:每次写新题,先手写三行“契约声明”
这是我带某高校编程社新人时,强制他们执行的规矩。它不增加代码量,却能避免 70% 的低级错误。每次打开编辑器,第一件事不是敲#include,而是用注释写下这三行:
// 【输入契约】:本题输入共 __ 行;第 1 行是 __;第 2 行起每行 __;以 __ 结束(如 EOF / 0 / -1) // 【输出契约】:对每组数据,输出 __ 行;每行格式为 "__";末尾 __(有/无)空行 // 【数据契约】:n 范围 [__ , __];a[i] 范围 [__ , __];时间限制 __ ms;内存限制 __ KB例如,写 1029(最大子段和)前,我会写:
// 【输入契约】:本题输入共 2 行;第 1 行是整数 n (1≤n≤10000);第 2 行是 n 个整数 a[i] (-1000≤a[i]≤1000) // 【输出契约】:输出 1 行;格式为一个整数,表示最大子段和;末尾有换行 // 【数据契约】:n 范围 [1, 10000];a[i] 范围 [-1000, 1000];时间限制 1000 ms;内存限制 65536 KB这三行,是你和 POJ 系统之间的“书面协议”。写代码时,每写一个scanf,就核对是否符合【输入契约】;每写一个printf,就核对是否符合【输出契约】;每定义一个数组,就核对大小是否满足【数据契约】。从那以后我每次写新题,都强制走一遍这三行契约,哪怕题目很简单。它让我少交了至少 20 次 WA,也让我在面对陌生题目时,第一反应不再是“好难”,而是“先签个合同”。希望帮到你。
本文还有配套的精品资源,点击获取