BISHI54 货物堆放¶
中等通过率 48.77%python3样例通过牛客 AC
讲解章节:贪心
一句话
安排堆放顺序,最小化 ∑(v_i - c_i * 上方总重)。
解题思路¶
这题考什么¶
又一道相邻交换求排序规则的贪心。 ∑v_i 是常数,所以「最小化总体积」== 「最大化 ∑ c_i * W_i」(W_i = 上方总重)。
看相邻两件 i、j,设它们上方总重为 P:
- i 在上:c_iP + c_j(P + w_i)
- j 在上:c_jP + c_i(P + w_j)
作差得 c_jw_i - c_iw_j。要让它 > 0(i 放上面更优),即 c_jw_i > c_iw_j, 也就是 按 w_i / c_i 降序排(压缩系数小、又重的货压在上面最划算)。
数据规模与复杂度¶
排序键:用精确整数键 (w << 128) // c,不用浮点 w/c,也不用 cmp_to_key
两个不同比值 w1/c1 != w2/c2 的最小间隔是
|w1*c2 - w2*c1| / (c1*c2) >= 1 / (c1*c2)。
而键取 floor(w * 2^128 / c) 相当于把比值放大 2^128 再截断,
量化步长 2^-128 ≈ 3e-39 远小于上面的最小间隔(c < 1e12 时也有 1e-24),
所以不同比值一定映射到不同整数且严格保序 —— **精确**,且只是一次
大整数移位 + 整除,n=1e5 实测 0.16s,比 functools.cmp_to_key 的
O(n log n) 次 Python 层函数调用快得多。
附:浮点键 w/c 其实也能被证明安全,但要靠一个不太直观的约束联动:
c_i < v_i / ∑w <= 1e12 / ∑w 且 ∑w >= max w,故 w1*c2 < 1e12,
误差项 2^-53 * (w1*c2 + w2*c1) < 2.2e-4 << 1,恰好压得住。
既然整数键同样快,就没必要把正确性押在这条边界分析上。
坑在哪¶
- c_i 可以是 0(题目允许 c_i >= 0),除零要单独处理:c=0 的货「怎么压都不缩」, 比值视作 +∞,排最前面(放最上面)。用一个大于任何真实键的哨兵 1<<200 表示;
- 排序方向别搞反,是 w/c 降序;
- v_i <= 1e12、n <= 1e5,总和可达 1e17,C++ 要 long long,Python 无所谓;
- 累加的是「上方的总重」,所以先用当前前缀重量算贡献,再把自己的 w 加进去。 顺序写反等于把自己的重量也压到自己身上,结果偏小;
- 输出的是体积总和 total_v - save,不是节省量 save 本身。
样例复核¶
三件货 (w,v,c) = (1,8,1)、(2,9,2)、(3,10,2),∑v = 27。 比值 w/c 分别是 1、1、1.5,降序排成 第三件 -> 第一件 -> 第二件 (前两件比值相同,谁在前都不影响结果)。 自上而下累计上方重量: 第三件:上方 0,节省 20 = 0,前缀重 3; 第一件:上方 3,节省 13 = 3,前缀重 4; 第二件:上方 4,节省 2*4 = 8。 共节省 11,答案 27 - 11 = 16,与样例一致 ✓。
自定义排序键的写法见 12-自定义排序, 相邻交换法见 47-贪心。
参考实现¶
[:octicons-arrow-left-16: BISHI53](BISHI53.md) [BISHI55 :octicons-arrow-right-16:](BISHI55.md)