Appearance
机试输出:格式与精度
场景引入
你把答案和标准输出并排贴到记事本里,一个字符一个字符地比,看起来完全一样,评测机却给了个红。
输出这一侧的判错有个共同点:肉眼几乎看不出来。差别藏在行末的一个空格、小数点后的第三位、或者一个大写字母上。这一篇把这些位置逐个点出来,并给出一套不用每次现想的写法。
一、行末空格与行间空行
要输出 n 个数,用空格分隔。最直觉的写法是每输出一个就跟一个空格:
c
for (int i = 0; i < n; i++) printf("%d ", a[i]);
printf("\n");这样最后一个数后面会多一个空格。多数评测机在比对时会忽略每行末尾的空白(我们自己的判题也是这么做的),所以这份代码通常能过。但这依赖于评测机的宽容度,而题面一般不会写明它宽不宽容。
不依赖这一点的写法是把分隔符放在元素前面,第一个除外:
c
for (int i = 0; i < n; i++) {
if (i > 0) putchar(' ');
printf("%d", a[i]);
}
putchar('\n');C++ 里同理:
cpp
for (int i = 0; i < n; i++) {
if (i) cout << ' ';
cout << a[i];
}
cout << '\n';多写一个 if,换来的是不用去猜评测机的脾气。
组间空行要读清楚题面的措辞,这两句话的含义不同:
- 「每组数据输出后跟一个空行」——最后一组后面也有空行
- 「两组数据之间用空行分隔」——最后一组后面没有空行
后一种的写法和上面处理行末空格是同一个套路,把空行放在每组之前,第一组除外:
c
int caseId = 0;
while (scanf("%d", &n) == 1) {
if (caseId++) printf("\n");
// 输出这一组
}二、浮点数:保留位数交给格式化函数
题面写「结果保留两位小数」时,不要自己去乘一百取整再除回来,直接交给格式化输出:
c
printf("%.2f\n", ans); // double 用 %f,不是 %lfcpp
#include <iomanip>
cout << fixed << setprecision(2) << ans << '\n';C++ 这一行有两个部分,缺一个都不对:
setprecision(2)单独用时控制的是有效数字位数,3.14159会输出成3.1- 加上
fixed之后,它才表示小数点后的位数
cout 不加任何设置时默认按 6 位有效数字输出,1234567.0 会变成 1.23457e+06。这是 cout 输出浮点数时最容易中招的地方。设置一次之后对后续输出一直有效,所以在 main 开头设好即可。
三、负零
计算过程里攒出来的浮点误差,会让一个数学上等于零的答案变成 -1e-13 这种极小的负数。格式化出来就是 -0.00:
c
double x = -1e-12;
printf("%.2f\n", x); // -0.00标准答案里写的是 0.00,于是判错。在输出前把误差量级的值归零:
c
if (fabs(ans) < 1e-9) ans = 0;
printf("%.2f\n", ans);几何题、带减法的实数题里,中间结果落到 -1e-16 这种量级是很自然的事,加上这一句成本很低。
阈值要选在比误差大、比任何有意义的数值小的位置。写 < 1e-9 时,-1e-12 会被归零,而 -1e-9 本身不会(比较是严格小于),所以别把阈值定得和你要处理的量级一样大。
还有一种 -0.00 不用处理:答案真的落在 -0.005 到 0 之间时,%.2f 输出 -0.00 是正确的四舍五入结果,标准答案同样如此。EPS 归零对这种情况也不生效,因为那个值在 1e-9 的尺度上并不算零。要修的只是误差造成的假零。
四、四舍五入不总是和手算一致
十进制小数在二进制浮点里多数存不精确。当一个数恰好卡在保留位的 .5 边界上时,格式化输出舍入的方向可能和手算的结果不同——因为存进去的那个值本身就已经比 .5 略大或略小了。
这件事没有一劳永逸的解法,但可以降低概率:
- 能用整数就别用浮点。金额乘一百按分算、比例通分成整数比较,都能绕开浮点。
- 判断两个浮点数相等要用差的绝对值和一个小阈值比较,直接写
a == b基本不会成立。
c
const double EPS = 1e-9;
if (fabs(a - b) < EPS) { /* 视为相等 */ }五、long long 的输出
数据范围超过约 21 亿就要用 long long,对应的格式串是 %lld:
c
long long ans = 1LL * n * m; // 注意 1LL,否则 n * m 先按 int 算就已经溢出了
printf("%lld\n", ans);1LL * 这个前缀容易漏。n * m 是两个 int 相乘,结果先按 int 计算再赋给 long long,溢出发生在赋值之前。
如果某个评测环境编译时不认 %lld,换成 cout << ans 即可,C++ 的 << 会按类型自己挑格式。
六、字面量要照抄题面
YES / Yes / yes 是三个不同的答案。题面给的是哪个就输出哪个,包括标点和空格。
Case #1: 42 这类带编号的格式尤其要照抄,冒号后面有没有空格、井号在不在,都是判错点。稳妥的做法是从题面的输出样例里复制这段格式,而不是照着敲一遍。
七、大量输出时避开 endl
endl 做两件事:输出换行,然后刷新缓冲区。刷新是有代价的,输出上万行时这个代价会累积成明显的耗时。
cpp
cout << x << '\n'; // 换行
cout << x << endl; // 换行 + 刷新日常输出用 '\n'。需要立刻看到输出的场合(比如交互题)才用 endl。
一套默认写法
把上面几条合成一个开头,抄下来直接用:
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout << fixed << setprecision(2); // 题目要几位就写几位
// ... 读入与计算
for (int i = 0; i < n; i++) {
if (i) cout << ' ';
cout << a[i];
}
cout << '\n';
return 0;
}开头那两行解绑的作用和注意事项,见下一篇。
延伸阅读
- 机试环境:IO 速度与内存边界 —— 解绑之后为什么不能再混用 printf
- 机试读入:多组数据与不定长输入 —— EOF、T 组、哨兵三种骨架
- 机试读入:字符串与混合格式 —— 换行符串行问题