
1. 题目分析与整体设计思路1.1 这道题到底在考什么信息学奥赛一本通里面的2067题题目短得不能再短输入圆的半径输出直径、周长、面积每个数保留4位小数。很多第一次接触竞赛编程的同学会觉得这道题“太简单了”不就是套公式吗但实际上这道题是第二章“顺序结构”里面的经典入门题它真正要考察的并不是你数学学得多好而是你对C最基本的输入输出、变量类型、格式化输出有没有真正吃透。先说人话这道题要求你完成三件事读入一个浮点数半径r然后用公式算出直径d 2r、周长c 2πr、面积s πr²最后用保留4位小数的格式输出三个数。听起来确实简单但如果你直接写printf(%f, d)那输出的就是6位小数不符合题目要求。如果你用cout来输出默认情况下根本不会保留4位小数。这些都是初学者最容易踩的坑。再说说为什么是“保留4位小数”而不是“保留两位”或者“不保留”。竞赛题目的输出要求非常严格多一位少一位都算错。这道题选择4位小数其实是故意让你去掌握printf的格式化控制符这也是整个信息学奥赛中最高频、最基础的输出技能之一之后的每一道题几乎都会用到。1.2 为什么选择 float 而不是 double题目给出的半径是按实数来输入的那么问题来了定义变量的时候用 float 还是 double很多教材在讲这道题的时候默认用了 float。为什么因为这是第二章的内容教材编写者希望你先接触单精度浮点型理解它和整型的区别。但我想说如果你真的想在这个领域走远一点从现在开始养成用 double 的习惯因为 double 的精度更高。竞赛评测数据有时候会很“刁钻”半径可能给到很大的数比如 12345.6789这种情况下 float 的有效数字只有7位左右计算出来的面积可能就已经开始出现误差了。这道题题目本身没有明确说用什么类型那么用 float 是能过的用 double 更是稳妥。我的建议是初学者先用 float 按题目默认的来因为你要学会看教材的意图但理解了之后自己写代码的时候尽量用 double。后面我会专门讲精度的问题这里先埋个伏笔。1.3 π的取值3.14159 不是随便定的圆周率π在题目里是直接给出的值是3.14159。这里有个细节为什么不是更精确的3.1415926535因为题目的评测标准就是按照3.14159来算的你如果自己用更精确的π去算得到的结果反而可能和标准答案对不上。我遇到很多同学上过数学课之后觉得3.14159“不够精确”自作聪明地写const double pi 3.1415926535;结果提交上去反而错了。原因很简单标准答案是用3.14159手算或者编程序算出来的你用的π和标准不一样最终结果自然会有细微差异卡在精度边界的时候就会被判错。这一点一定记住竞赛题目的要你“用题目给的精度”不是要你“用数学上的最精确”。const double pi 3.14159; // 题目给多少就用多少2. 核心语法与实现细节解析2.1 输入输出格式的精确控制这道题的核心考点本质上就是格式化输入输出。先看输入半径是一个实数所以用scanf(%f, r)还是scanf(%lf, r)取决于你定义的是 float 还是 double。这个对应关系简直是我见过的初学者犯得最多的错误用 double 定义了变量却用%f去读或者用 float 定义了变量却用%lf去读。虽然在某些编译器上碰巧能跑出正确结果但本质上这是未定义行为评测机上很可能出错。再看输出题目要求保留4位小数那就必须用printf(%0.4f, x)这种格式。%0.4f的含义拆开来看就是%表示后面是一个格式控制符0.4表示小数点后保留4位f表示以浮点数形式输出。有同学会问为什么不是%4.0f因为4.0表示的是整个数至少占4个字符宽度而小数点后保留0位那输出的就是整数形式了完全不是一回事。这两个写法顺序一换结果天差地别初学的时候一定要反复对比。2.2 开发环境与头文件选择这道题用 C 语言写和用 C 写都能过但我建议初学者统一用 C 的写法因为信息学奥赛的主流语言就是 C。你需要的头文件是cstdio因为printf和scanf定义在里面。如果你用了iostream那它是给cin和cout用的不写cstdio直接用printf在有些编译器上会报错提示找不到函数。#include cstdio using namespace std; int main() { // ... return 0; }注意using namespace std;这个语句。对这道题来说即使不写也能过因为printf和scanf是 C 标准库的函数不在std命名空间里。但这个习惯必须养好因为之后用cin、cout、string、vector的时候没有这个语句会非常痛苦。当然严格来说printf在 C 中也可以在std命名空间里找到std::printf也是合法的但从实际做题的角度写不写using namespace std;对本题影响不大。2.3 公式背后的“为什么”这道题涉及三个数学公式直径 d 2r周长 c 2πr面积 s πr²。可能有同学觉得我在侮辱智商这种公式谁不知道但问题在于编程里的“公式”和数学里的“公式”有一点点不同。用 C 表达平方不是直接写r * r就行了吗对这道题直接写r * r是最可靠的方式。但有些同学会想到pow(r, 2)这就引入了一个新函数pow它的头文件是cmath而且返回的是 double 类型在这道题里虽然也能过但一来增加了头文件依赖二来在竞赛中pow的精度和速度都不如直接乘法。这一题用“乘法”表示平方是最朴素的道理但也是很多初学者需要刻意去记住的能用四则运算解决的问题尽量不要引入函数。还有一个点。三个量的计算顺序上直径、周长、面积彼此独立所以先算谁都行。但如果你图省事写出s pi * d * d / 4这样的式子虽然数学上完全正确因为 r d/2但在浮点运算中会多一次除法增加不必要的计算量和误差。最简单的就是严格按照题目给的公式来c 2 * pi * rs pi * r * r。不要自我发挥。3. 完整代码实现与逐行讲解3.1 标准参考答案这道题的标准代码我给出两个版本。第一个是 float 版本贴合教材原意第二个是 double 版本更稳健适合真正上考场用。// 版本一float 版本 #include cstdio using namespace std; int main() { float r; scanf(%f, r); float d 2 * r; float c 2 * 3.14159 * r; float s 3.14159 * r * r; printf(%0.4f %0.4f %0.4f\n, d, c, s); return 0; }// 版本二double 版本推荐 #include cstdio using namespace std; int main() { double r; scanf(%lf, r); const double pi 3.14159; double d 2 * r; double c 2 * pi * r; double s pi * r * r; printf(%0.4f %0.4f %0.4f\n, d, c, s); return 0; }3.2 逐行拆解代码逻辑第一行#include cstdio引入了输入输出函数的声明。第二行using namespace std;为了之后使用标准库方便。main函数是程序入口这个不用多说但你要注意竞赛代码里main函数几乎都是int类型最后return 0;表示程序正常结束。这个习惯必须从第一行代码开始养成。然后scanf(%f, r);这一句注意r前面有一个符号。这个取地址符号初学者经常漏掉。它的含义是“把读到的值放到 r 这个变量所在的内存地址里”。如果漏掉程序不会报编译错误但运行时行为是未定义的你得到的结果几乎一定是错的。这种错误特别隐蔽排查起来非常痛苦因为编译器不提示运行也不一定会崩溃就是结果不对。接下来就是三个计算式。这里有一个坑你可能已经发现了2 * r里的2是整数r是浮点数C 会自动把整数转换为浮点数再进行乘法所以没问题。但如果反过来你定义int r;然后再scanf(%f, r)那就有问题了因为%f会往一个整型变量的内存里写数据数据解释完全乱套。记住scanf的格式符必须和变量类型严格匹配。最后printf输出。三个%0.4f用空格分隔输出的就是类似6.2832 12.5664 12.5664这样的格式。题目要求输出三个数并且用空格隔开这里末尾的\n表示换行。有些同学会纠结末尾到底要不要换行我明确告诉你要。绝大多数在线评测系统习惯在输出的末尾有一个换行符虽然不写有时候也能过但你永远不知道哪次评测会严格起来。3.3 样例验证与实际运行题目通常会给你一个样例输入比如输入1输出2.0000 6.2832 3.1416我们来手算验证一下。半径 r 1 时直径 d 2 × 1 2保留4位小数就是 2.0000。周长 c 2 × 3.14159 × 1 6.28318保留4位小数看第5位小数是 8所以四舍五入变成 6.2832。面积 s 3.14159 × 1 × 1 3.14159保留4位小数看第5位是 9四舍五入变成 3.1416。注意这个“四舍五入”是 printf 帮你做的你不需要自己写判断。但你要理解编程里的“保留4位小数”默认就是四舍五入比如 2.00005 保留4位会变成 2.0001。这个机制在浮点数边界上偶尔会有一些反直觉的现象比如2.675保留两位会变成2.67而不是2.68但那是二进制存储精度的锅不是 printf 的锅。这道题的数据范围不会触发这么极端的边界问题所以你暂时不用操心。4. 常见错误与调试技巧实录4.1 我做这道题时踩过的坑我第一次做这道题的时候犯过一个非常蠢的错误把%0.4f写成了%4.0f。当时我盯着输出结果看了半天怎么输出都是个整数完全不是我想要的。后来才发现%4.0f的意思是“整个数占至少4位宽度小数部分0位”相当于直接四舍五入成整数了。还有一个印象深刻的错误我一开始定义的是float r;但输入的时候写成了scanf(%lf, r);。程序运行后读入的数值完全乱套输出了一个天文数字一样的垃圾值。这是因为%lf告诉 scanf 往一个 8 字节的空间里写数据但float r只占 4 字节后面的 4 字节就写到了别的内存里整个栈都被破坏了。这种错误一出现很难一眼看出来我的排查方法是先把输出语句注释掉单独看scanf是否读对了值。4.2 检查清单提交前过一遍这道题虽然简单但很多同学提交了一两次都是 Wrong Answer其实不是不会做而是细节出了问题。我总结一个提交前的检查清单你们以后可以做任何简单题的时候照着过一遍检查项说明变量类型与格式符匹配float配%fdouble配%lf绝对不能混scanf是否漏写凡是读入变量必须写变量名输出格式是否精确%0.4f才是保留4位小数%4.0f是整数公式是否用了题目给的π不要私自改成3.1415926是否有换行输出末尾建议带上\n头文件是否齐全用了printf就要#include cstdio变量是否初始化不要依赖编译器自动清零竞赛环境不保证4.3 一个隐秘的坑整数除法有些同学会把面积公式写成s pi * r * r;这没问题。但如果有同学借鉴其他题目的经验想用r^2的表达方式比如r ^ 2那就会翻车。在 C 中^是按位异或运算不是幂运算。r ^ 2的结果是整数按位异或跟 r 的平方没有任何关系。对于 r 33 ^ 2的结果是 1二进制 11 异或 10 01你如果发现了这个结果是1可能整个人都懵了。还有同学会把周长写成c 2 * pi / r这种错误属于笔误但调试起来很费时间。我的建议是写公式的时候一行一行地写不要为了节省代码行数把三个公式挤成一行。多一个回车不扣分但能帮你减少一半的笔误概率。5. 从这道题延伸出去竞赛新手最容易忽略的三件事5.1 整数溢出与浮点精度的边界感很多新手学完这道题以为自己学会了“会套公式就会做题”。但真正到了后面的题目你会发现“精度”和“溢出”是两个永远躲不开的话题。以这道题为例如果半径 r 很大比如 100000那么面积 s 3.14159 × 100000 × 100000 31415900000000也就是 3.14 × 10^13。这个数字已经超过了 32 位整数int的最大值 2147483647所以如果你把面积定义成 int就会溢出变成一个奇怪的负数。而 float 虽然能存这么大的数但有效数字只有 7 位后面的细节全丢了。到了这种边界double 的优势就很明显了它能精确表示约 15~16 位有效数字。所以我的习惯是只要题目涉及实数运算一律用 double不要用 float。虽然 float 看起来更“省内存”但在竞赛那种数据规模下内存从来不是最大瓶颈精度才是。等到你开始做分数类、高精度类的题目时你会更加深刻地体会这一点。5.2 学会从“教科书写法”走向“竞赛最优写法”教材上的标准写法是让你看懂思路但竞赛中追求的是“最稳的写法”。什么意思以本题为例教科书会写printf(%0.4f %0.4f %0.4f\n, d, c, s);但你如果去问一个省一选手他会告诉你他可能根本不用printf而是直接用cout配合iomanip头文件里的fixed setprecision(4)。#include iostream #include iomanip using namespace std; int main() { double r; cin r; const double pi 3.14159; double d 2 * r; double c 2 * pi * r; double s pi * r * r; cout fixed setprecision(4) d c s endl; return 0; }这个写法在功能上和printf版本完全等价但风格上更“C”不需要自己去记%f和%lf的区别。我个人的建议是两种写法都要会。因为printf的格式化输出在很多场合比如输出宽度、补零、进制转换更强大而cout的setprecision更直观配合fixed就是固定保留小数位数。很多省赛和国赛选手其实两种混着用看心情但前提是两种都不会出错。5.3 关于“弗洛伊德算法”的热搜词的说明可能有些同学在搜索“信息学奥赛一本通”的时候会看到“弗洛伊德算法”这个关键词出现在关联搜索里。这里我得提醒一句弗洛伊德算法Floyd是图论中求多源最短路径的经典算法和这道“圆”的入门题没有任何关系。它出现在热搜关联里大概率是因为《信息学奥赛一本通》这本书在后面章节里有弗洛伊德算法的例题很多人在搜题解。这从侧面说明一个事实一本通这本书是很多竞赛选手的“题海库”从第二章的入门题到后面的图论难题跨度非常大。你现在能对着2067题认真抠细节等到你做到弗洛伊德那几题的时候就会感谢现在养成的严谨习惯。6. 结个尾这套题做完后下一步怎么走这道题本身并不难但它背后的几件事——格式化输出、浮点数精度、变量类型匹配——是信息学奥赛里最核心的几块基石。很多人都觉得“这么简单还要学吗”但现实中我看到太多新手在这类简单题上反复提交十几二十次最后发现自己挂在%lf写成了%f这种细节上。这种错误不丢人丢人的是错了之后还不明白为什么错。根据我的经验你把2067题吃透之后下一步可以试试把题目改成“保留4位小数但是输出在同一行每个数占10个字符宽度右对齐”。你会发现printf(%10.4f, x)和printf(%-10.4f, x)的区别会逼着你再去翻一遍格式化输出的手册。这种“自己给自己出题”的训练方式比刷十道新题更有用。还有一个小技巧可以分享每次通过一道题目之后不要急着走把代码里的第二组数据比如r 2、r 3.14拿来跑一遍看看输出的结果是否符合你的直觉。这个习惯可以帮你建立“计算预期”的能力以后到了涉及二分答案、浮点二分等高级算法的阶段这个能力会让你调试起来比别人快很多。这道圆就是你竞赛路上真正意义上的第一个“圆”从这里开始后面的路会越来越宽。