跳转至

BISHI14 特殊的科学计数法

简单通过率 29.28%python3样例通过牛客 AC

牛客原题  源码

讲解章节数据类型与转换浮点与科学计数法

一句话

把最多 1e5 位的大整数写成 a.b*10^c 的形式。

解题思路

这题考什么

「大数只按字符串看」的意识,外加一次严格的四舍五入。 N 可以有 1e5 位,一旦 int(N) 再转成浮点数就全毁了:float 只有约 16 位 有效数字,后面的位直接消失,指数也可能溢出成 inf。 但题目真正需要的信息只有三样:

c = 位数 - 1                 (最高位的权重,即指数)
a = 第 1 位数字               (题目保证 N >= 100 且无前导零,故 a ∈ [1,9])
b = 第 2 位按第 3 位四舍五入后的结果

也就是说,答案只与前 3 位数字和总位数有关,其余 99997 位一概不影响。 读入之后取个切片就够,复杂度就是读入本身的 O(|N|)。 浮点与舍入的背景见 23-浮点与科学计数法

数据规模与复杂度

|N| <= 1e5,一次读入 + 常数次运算,O(|N|)。 时限「其他语言 6 秒」对这点计算量绰绰有余,难点全在正确性上。

坑在哪

  1. 不能用 round() 或 "%.1f" 做四舍五入,两者会从两个方向出错。 其一,round 用的是「四舍六入五成双」的银行家舍入,恰好是 0.5 时向偶数 靠拢:round(1.25, 1) 得 1.2,而题目要的是 1.3。 其二,二进制浮点存不下大多数十进制小数,9.95 存进 float 后实际是 9.9499999999999992...,于是 round(9.95, 1) 和 "%.1f" % 9.95 都给出 9.9, 而正确结果是进位到 10.0 再规格化成 1.0。 正确做法是 Decimal + ROUND_HALF_UP,且必须从字符串构造 Decimal, 一旦中途经过 float,误差在构造时就已经引入了;
  2. 进位可能溢出成 10.0(例如 N = 995... 时前三位是 995,9.95 进位成 10.0)。 题目要求 a ∈ [1,9],此时要重新规格化为 1.0,并把指数 c 加 1;
  3. 只看第 3 位就能定舍入方向:HALF_UP 语义下,第 3 位 >= 5 一定进位、 < 5 一定不进位,第 4 位往后有多少位、是什么都不改变结果。 所以切前 3 位是等价的,不是近似;
  4. 输出格式是 a.b*10^c,星号和尖号都不能少、也不能有空格; Decimal 经过 quantize(Decimal("0.1")) 后固定保留一位小数, 所以 3 会打印成 "3.0",这正是题目要的形式。

样例复核

N = 299792458:位数 9,c = 8;前三位 "299" -> 2.99 -> HALF_UP 到一位小数 得 3.0,输出 3.0*10^8,与样例一致。

参考实现

solutions/BISHI14.py
import sys
from decimal import Decimal, ROUND_HALF_UP

s = sys.stdin.buffer.read().split()[0].decode()
c = len(s) - 1                              # 指数 = 位数 - 1(题目保证无前导零)

head = (s + "00")[:3]                       # 题目保证 N >= 100,这里再补零以防万一
# 把前三位写成 "a.bc" 再舍入到一位小数:第 3 位就是唯一决定进位与否的那一位
v = Decimal(head[0] + "." + head[1:]).quantize(Decimal("0.1"), rounding=ROUND_HALF_UP)
if v >= 10:                                 # 9.95 这类进位溢出,重新规格化
    v = Decimal("1.0")
    c += 1

sys.stdout.write("{0}*10^{1}\n".format(v, c))
[:octicons-arrow-left-16: BISHI13](BISHI13.md) [BISHI15 :octicons-arrow-right-16:](BISHI15.md)