
PA8这道题题面往往只有三行不到给你 n 天的气温求平均气温。但就是这么一道看起来人畜无害的小题我在某算法训练系统里做过统计新学员第一次提交的通过率不足一半。原因不外乎三类数据范围没看清导致溢出、平均值用整数除法算错、输出格式跟题面要求差一位小数。今天这篇题解报告就把平均气温Temperature这道题从头到尾拆开揉碎从输入处理、类型选择、精度控制、边界测试四个层面逐项过一遍。目标是让你不只 AC 这一道题还能把这一类“求平均”题目的通用解法都掌握住。无论你是刚学编程的新手还是为了竞赛集训刷题的老手这篇报告都值得花五分钟对照你的代码看一遍。1. 一眼看穿题目本质PA8到底在考什么1.1 先把题面还原出来PA8 在不同平台上的描述略有差异但核心几乎一致。我把最常见的版本写出来输入格式第一行是一个整数 n表示需要统计的气温天数。第二行有 n 个数值表示每天的气温气温可以是整数也可以带一位小数且允许出现负数。输出格式输出一个实数表示这 n 天的平均气温四舍五入保留 1 位小数。有些变体会把第二行拆成 n 行每行一个气温还有一些版本会让你额外统计“气温高于平均值的天数”。但不管怎么变题目的数学本质就是一个加权平均数的计算考的是你处理输入、选择数据类型、控制输出格式的基本功。这道题的难度标签通常是“入门”可为什么通过率会那么惨我观察下来主要原因是新手把注意力全放在“求平均”这个数学动作上忽略了程序题和数学题的本质区别。数学题里你写完sum / n就结束了程序题里输入怎么读、存成什么类型、输出怎么格式化、数据大了会不会溢出每一个环节都可能让你前面的正确计算白费。1.2 看似简单的题考点藏在三个细节里题面越短容易被忽略的信息就越多。平均气温这道题真正决定 AC 还是 WA 的是下面三个细节。第一个细节是输入形式。气温序列是放在一行里还是每个占一行如果是放在一行里你用scanf(%d)循环读 n 次也没问题因为scanf天然跳过空白字符如果你用input().split()这类方式就要确认数据会不会跨行。很多题目为了照顾评测格式可能把大量数据折行输出这时候“只读一行再 split”的代码就会漏数据。第二个细节是数据范围。n 可能大到十万甚至百万单个气温可能是-50到50也可能上万。用int累加会不会爆我见过不少选手拿int sum去累加十万个五千度的高温模拟数据结果累计值轻松突破 32 位整数上限WA 得毫无悬念。第三个细节是输出格式。保留一位小数听起来简单但 C 语言的printf(%.1f)、C 的fixed setprecision(1)、Python 的f{avg:.1f}四舍五入的行为在某些边界值上并不完全一致。你要是用错了格式化方式本地看着没问题提交上去就是错。所以我把这道题的定义概括成一句话它不考你会不会算平均数考的是你会不会在程序里把“读、存、算、输”四个环节都做到滴水不漏。2. 核心实现三步走从输入到输出2.1 第一步选择数据存储方式拿到题目先别急着写循环想清楚一个问题我需要把数据存下来吗如果题目只要求输出平均值那完全可以不存数组读一个累加一个。这样空间复杂度是 O(1)代码也更简洁。C 语言版可以这样写#include stdio.h int main() { int n; double sum 0.0; double temp; scanf(%d, n); for (int i 0; i n; i) { scanf(%lf, temp); sum temp; } printf(%.1f\n, sum / n); return 0; }但如果题目要求“统计气温高于平均气温的天数”那就必须存数组因为你不先算出平均值就不知道哪些天是高于平均的。这时候空间复杂度就是 O(n)必须事先开好足够大的数组。注意这类题目的数据范围比如 n 最多 100000那数组至少要开 100005宁可多开一点也不要卡着上限开。#include stdio.h int main() { int n; double a[100005]; double sum 0.0; scanf(%d, n); for (int i 0; i n; i) { scanf(%lf, a[i]); sum a[i]; } double avg sum / n; int cnt 0; for (int i 0; i n; i) { if (a[i] avg) cnt; } printf(%.1f\n%d\n, avg, cnt); return 0; }这个判断是写题前必须做的。很多新人一上来就开数组导致空间浪费还有些人只在需要二次遍历的时候才意识到“哎呀我前面没存”只能重写。我个人的习惯是先看输出需要哪些信息再决定要不要数组。2.2 第二步平均值计算的精度控制平均值计算本身不复杂公式就是sum / n但这里藏着这道题最大的一个坑类型转换。如果你声明了int sum 0然后执行printf(%.1f\n, sum / n)这里的sum / n先用整数除法计算得到的结果是截断后的整数然后才转换成double传给printf。比如三天的气温是 10、20、3010 20 30 6060 / 3 20正好整除看不出来问题。但如果气温是 10、20、31sum 61整数除法61 / 3 20输出就会是20.0而正确结果是20.3。正确的做法有两种一种是像上面的代码那样从一开始就把sum声明成double另一种是除法时做显式转换写成(double)sum / n。我推荐第一种因为累加过程用浮点也能避免溢出不过要注意浮点累加的精度损耗这一点后面专门讲。另外一个细节是除 n 的时间点尽量往后放。有的新手喜欢在每个数据读进来以后先除以 n 再加总写出来是avg temp / n。从数学上看结果一样但在计算机里temp / n会先做一次浮点除法引入舍入误差累加 n 次以后误差会被放大。而先用sum temp把原始数据累加完最后只做一次除法误差要小得多。2.3 第三步输出格式化输出一位小数三种常用语言各写一版方便对照。C 语言printf(%.1f\n, avg);C 需要同时设置fixed和setprecision否则setprecision控制的是有效数字位数而不是小数位数#include iostream #include iomanip using namespace std; int main() { int n; double temp, sum 0.0; cin n; for (int i 0; i n; i) { cin temp; sum temp; } cout fixed setprecision(1) sum / n endl; return 0; }Python 直接使用格式化字符串n int(input()) temps list(map(float, input().split())) avg sum(temps) / n print(f{avg:.1f})这里有个 Python 特有的陷阱后面章节专门讲。输出格式化这一环最重要的原则是严格按题面要求来不要自己发挥。题面要求保留 1 位小数你就不要输出20或20.00要求每个输出占一行你就一定要加换行符。很多题目判分对格式极其严格多一个空格都可能报 WA。3. 三个最隐蔽的坑精度、溢出与边界3.1 浮点数的有效位与比较陷阱平均气温这道题用float还是double我的答案永远是用double。float只有大约 7 位有效十进制数字累加几万个数之后最低位的误差会逐渐累积可能影响你输出的一位小数。double有大约 15 到 16 位有效数字在这道题的数据范围内很少出问题。但double也不是万能的。最典型的场景是判断“某一天气温是否高于平均值”。你去写if (a[i] avg)在数据量小的时候没问题可是如果两个数一个是平均值本身、一个因为浮点误差比平均值多出1e-15它就会被判定为“高于平均”导致计数多 1。如果你在题目里遇到这种精度敏感的情况比较前可以先做一个容差处理比如if (a[i] - avg 1e-9) { cnt; }当然 PA8 的基础版本很少会卡到这个程度但我见过不少延伸题在这里翻车。提前理解浮点比较的原理比事后调试半天要实用得多。3.2 整型溢出的经典场景刚才提过一次溢出这里展开说。假设 n 的最大值是 100000气温的最大值是 100000那sum的理论最大值是10^10这个数远超出了 32 位有符号整数的上限2147483647。如果你用int存累加和直接溢出成负数平均值自然就错了。不要觉得这种极端数据不会出现。评测系统的测试点里一定有全最大值的数据专门用来拦你这种粗心。我见过有人辩解说“题目里的气温范围是 -50 到 50n 是 100000那 max sum 也就 5000000int 够用了”。确实够用但问题是这个“够用”的判断是你读了题面数据范围之后得出的很多新手根本不读题面范围只看到“平均值”三个字就自以为会了。稳妥的做法是只要 n 超过 1e5累加变量一律用double或long long。double还能顺便解决除法类型问题一举两得。3.3 边界条件的特判边界条件不是每次都会考但每次考都能淘汰一大片。平均气温这道题至少有三种边界情况值得你提前造数据自测。第一种n 等于 1。只有一个数据点时平均值就是它本身输出格式别出错。比如输入1 -5正确输出是-5.0。第二种气温全部为负数。平均值可能是-0.0这种让人摸不着头脑的值。有些语言在极少数情况下会输出-0.0肉眼看着没问题评测却可能判错因为文本-0.0和0.0不一样。如果题目保证平均气温不为 0那无所谓如果不保证你要留意这个问题。第三种输入数据中包含多位数的小数比如-12.5 23.75 0.125。你的读取格式要能正确处理小数点而不是只按整数读。C 语言里用%lf读 double 天然支持小数但如果你用了%d去读就会把12.5读成 12后面的.5还会扰乱后续输入直接导致全部错乱。4. 本地全过、提交就WA问题排查实录4.1 五大WA原因速查表我在带新人做这道题的时候把常见的 WA 原因整理成了一张速查表。你如果提交没过对照这张表一个个查比自己瞎猜效率高得多。症状常见原因排查方向编译错误头文件缺失、变量名拼写错误看编译器第一行报错信息运行时错误数组越界、除数为零检查数组大小是否足够、n 是否为 0答案错误整数除法截断检查sum的类型和sum / n的写法答案错误输出格式不符对比题面样例注意空格、换行、小数位数答案错误精度不够把float换成double答案错误四舍五入规则理解偏差用临界数据测试printf和format行为答案错误输入读取方式漏数据确认数据是否跨行改用循环逐个读取这张表里的每一项都是真实出现过的。尤其是最后一条很多新手用input().split()读取后又直接调用map结果遇到跨行输入时只读到第一行数据后面的全丢了但本地测试的时候数据恰好都在一行所以怎么测都正确。4.2 一个真实的排查过程还原去年集训队有个 A 同学代码写得很快五分钟交了第一版int n, sum 0, t; scanf(%d, n); for (int i 0; i n; i) { scanf(%d, t); sum t; } printf(%.1f\n, sum / n);本地跑样例输入三天的气温 10 20 31输出20.0。他自己手算了一遍61 / 3 20.333...觉得保留一位小数应该输出20.3但程序输出20.0可他没意识到这是整数除法的问题以为是自己看错了题。提交上去WA。我让他加了一句调试代码把sum的类型打印出来又打印sum / n的结果他才发现sum / n在 C 语言里是先算整数除法再转double61 / 3的整数结果是20小数部分被完全截断。把sum改成double之后第二次提交就 AC 了。还有一个 B 同学用的是 Java所有计算都对输出也保留了一位小数但就是 WA。他把代码贴给我看我发现他用的是System.out.print(avg)而不是System.out.printf(%.1f, avg)。虽然他前面已经用DecimalFormat处理过但因为没保留足够的有效位输出了一位整数和题面要求的“保留 1 位小数”不一致。这就是典型的输出格式问题不是算法问题。4.3 构造边界数据的测试清单很多新手 AC 了就急着做下一题我建议你反过来AC 之后再多跑几组极端数据确认代码是真的稳而不是碰巧过。下面这组测试清单是我个人一直用的也分享给你1 -5检查单个负数温度是否输出-5.0。3 10 20 30检查整除情况下输出是否为20.0。3 10 20 31检查非整除时是否输出20.3而不是20.0。2 2.25 2.25检查临界小数在输出一位时是否符合预期。5 0 0 0 0 0检查全零时输出是否为0.0。构造一万个相同大数检查累加和是否溢出。每一组数据跑完你都能对代码的健壮性多一份把握。这套测试清单不光适用于平均气温任何“求和再平均”类题目都可以直接套用。5. 延伸不存数组的在线平均与实用场景5.1 什么时候必须流式处理前面我们说过如果只求平均值可以不存数组读一个累加一个。但有一种场景更极端数据量巨大n 可能有几千万或者数据来源是实时采集的传感器你根本没办法“先读进来再统一计算”。这时候你就需要一种一边读入一边更新平均值的算法专业一点叫“在线平均”或“流式平均”。PA8 这道题本身不会考到这么大的数据量但理解这个概念对你做后面的题很有帮助。比如一个气象站每分钟上报一次气温你要持续计算历史平均温度同时系统内存很小不能把所有历史数据存下来。在线平均就是唯一合理的解法。5.2 递推更新均值公式在线平均的核心是一个递推公式。假设你已经处理了 k 个数据当前平均值是avg_k现在来了一个新数据x那新的平均值可以这样算avg_{k1} avg_k (x - avg_k) / (k 1)这个公式的原理很简单新平均值等于旧平均值加上新数据带来的修正量。修正量的大小是(x - avg_k) / (k 1)也就是新数据与旧平均值的偏差被新的数据量稀释。用代码写出来就是cnt 0 avg 0.0 while True: x read_temp() cnt 1 avg avg (x - avg) / cnt print(f{avg:.1f})这段代码从头到尾只用了两个变量cnt和avg不管数据来多少条内存占用都不变。5.3 数值稳定性对比有人可能会问这个递推公式一直累积误差会不会比一次性算完差很多我的实测结论是在气温这种小数场景下差异小到可以忽略。double的精度足够支撑上千万次递推误差一般不会超过1e-9。但如果你对精度有强迫症或者题目数据本身数值差异特别大可以用一种叫 Kahan 累加的技巧在每次加法的同时补偿丢失的低位信息。不过那是另一个话题了对于平均气温这道题用简单的递推公式已经足够稳。我个人在这道题上踩过最大的坑是自以为是地用float累加了几万条数据结果平均值差了 0.1输出去就是 WA。从那以后凡是涉及累加求平均我默认double凡是写除法我一定盯住类型转换。这里再分享一个自己的习惯题目 AC 之后不急着下一题而是多构造几组极端数据跑一遍比如 n 取 1、温度全是零、温度全负、输入包含最多位数的小数。这种“笨办法”练多了很多边界问题根本到不了提交环节就被你提前埋掉了。希望这份题解报告能帮你把平均气温这道题彻底吃透也把求平均这一类题的基本功打牢。