把问题退化成 6×6 方格切割:6*6、5*5、4*4 各占一箱、3*3 每四个一箱,再用查表算出剩余 2*2 槽位,最后按总面积补 1*1。
OJ: roj
题目 ID: 1226
难度:普及-
标签:贪心模拟python
创建: 2026-10-07 15:39
更新: 2026-10-07 15:39
形式化题目
有六种方形产品,边长分别为
输入是若干行,第
其中 0 0 0 0 0 0 一行表示输入结束,该行不产生输出。
对每个订单,求装下全部产品所需的最少包裹数,每行输出一个整数。
样例的两个订单分别是
正解
思路
从最大的开始放,能定下来的先定下来。 关键观察是:
- 边长
的正方形正好铺满一整箱,一个 必须独占一个包裹; - 边长
的正方形占掉一整行还剩一条宽 的缝,只能放 ,放不下任何 ; - 边长
的正方形占掉一整条 ,剩下 格,最多放 个 ( 格放不下 个,因为每个 占 格且需要成对的行列); - 边长
的正方形一箱最多放 个( 排列),所以 的数量每四个凑一箱。
于是贪心分三层:
第一层:大件定箱数。
第二层:算已开包裹里还剩多少
- 每个
旁边能放 个 (只有 宽的缝); - 每个
旁边能放 个; - 装
的箱子里放完 个 后( 表示这一箱被 个 占满),剩下的空间能放多少个 如下表:
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 剩余可放 |
0 | 5 | 3 | 1 |
即 two_slots = 5 * c4 + LEFT_FOR_TWO[c3 % 4]。注意:
第三层:
它就是所有包裹加起来还能放下的
为什么贪心是对的? 每一步的选择都是被迫的,可以用交换论证说明:
放不下第二个任何型号,必须独占; 与 同理不能与 同尺寸或更大的共箱,所以 是硬下界,且取到这个下界不会挤掉任何 更小的产品(它们只占用"剩余空间",而剩余空间无论如何都用不完)。 - 每个
必须占一整行或一整列的 格条带,一箱最多容纳 个, 所以 也是硬下界。既然箱数已由 定死, 就按 "每箱塞满 个、最后一箱留 个"摆放,这时留出的 槽位最多。 只用已开箱的槽位是免费的,能塞就塞;塞不下的部分只能开新箱, 每箱 个又是硬上界,所以 最优。 - 最后
是最小单位,任何空位都能填,按总面积计算既不会浪费也不会冲突。
三层各自取到下界,且互不抢占对方的必要空间,所以累加结果即最小包裹数。
样例追踪。 对订单 0 0 4 0 0 1:第一层 two_slots = 0;one_slots = 72 - 36 - 36 = 0,
7 5 1 0 0 0:第一层
two_slots = LEFT_FOR_TWO[1] = 5,
恰好放下 one_slots = 36 - 9 - 4 \times 5 = 7,
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-07 15:39
# update_at: 2026-10-07 15:39
import sys
from itertools import islice
# 一箱里放 k 个 3*3(k = 0,1,2,3)之后,还能塞下的 2*2 个数
LEFT_FOR_TWO = (0, 5, 3, 1)
END = (0, 0, 0, 0, 0, 0) # 输入结束标志:六个 0 的一行
ORDER_LEN = 6 # 每个订单占 6 个整数(1*1 到 6*6)
# 类型别名(Python 3.12+ 的 type 语句),相当于 C++ 的 using / typedef
type Order = tuple[int, ...] # 一个订单的六个型号数量(1*1 到 6*6)
def ceil_div(a: int, b: int) -> int:
"""向上取整的整数除法。"""
return -(-a // b)
def min_boxes(order: Order) -> int:
"""求一个订单所需的最小包裹数。"""
c1, c2, c3, c4, c5, c6 = order
# 6*6、5*5、4*4 各占一个包裹,3*3 每四个占一个
boxes = c6 + c5 + c4 + ceil_div(c3, 4)
# 已开的箱子里被大件腾出来的 2*2 槽位:5*5 旁只能放 1*1,4*4 旁能放 5 个,
# 3*3 按每箱剩余 0/1/2/3 个分别是 LEFT_FOR_TWO 里的 0/5/3/1
two_slots = 5 * c4 + LEFT_FOR_TWO[c3 % 4]
# 槽位不够就再开新箱(每箱 9 个 2*2),够则为 0
boxes += max(0, ceil_div(c2 - two_slots, 9))
# 总面积扣掉大件已占的面积(36/25/16/9/4 对应 6*6 ~ 2*2),剩下的全归 1*1
one_slots = 36 * boxes - 36 * c6 - 25 * c5 - 16 * c4 - 9 * c3 - 4 * c2
# 剩余空间不够就再开新箱(每箱 36 个 1*1)
boxes += max(0, ceil_div(c1 - one_slots, 36))
return boxes
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
out: list[str] = []
while True:
order = tuple(islice(data, ORDER_LEN)) # 一次读满一个订单
if not order or order == END: # 六个 0 是结束标志,不产生输出
break
out.append(str(min_boxes(order)))
# 全空输入(只有结束行)不输出任何字符
sys.stdout.write('\n'.join(out) + ('\n' if out else ''))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-07 15:39
* update_at: 2026-10-07 15:39
*/
// 装箱问题:6*6*h 的包裹里塞 1*1 ~ 6*6 的六种方形产品,求最小包裹数。
// 思路:每种产品都占满包裹的高度,问题退化成二维的 6*6 方格切割,
// 大件(4*4 及以上)必须单独占一个包裹,3*3 每四个一箱,剩下的空位才用 1*1、2*2 填。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
// left_for_two[k] = 一箱里放 k 个 3*3(k = 0..3)之后,还能塞下的 2*2 个数
int left_for_two[4] = {0, 5, 3, 1};
// 循环处理每个订单:读入六个型号的数量,输出最小包裹数
int main() {
ll cnt1, cnt2, cnt3, cnt4, cnt5, cnt6;
while (cin >> cnt1 >> cnt2 >> cnt3 >> cnt4 >> cnt5 >> cnt6) {
if (cnt1 == 0 && cnt2 == 0 && cnt3 == 0 && cnt4 == 0 && cnt5 == 0 && cnt6 == 0) {
break; // 六个 0 是输入结束标志,不产生输出
}
// 第一步:6*6、5*5、4*4 各占一个包裹,3*3 每四个占一个
ll boxes = cnt6 + cnt5 + cnt4 + (cnt3 + 3) / 4;
// 第二步:统计当前所有包裹里被大件腾出来的"2*2 槽位"总数。
// 5*5 旁边只能放 1*1;4*4 旁边能放 5 个 2*2;
// 3*3 按每箱剩余个数 0/1/2/3 分别能放 0/5/3/1 个 2*2。
ll slots_two = 5 * cnt4 + left_for_two[cnt3 % 4];
// 2*2 槽位不够就再开新箱,每箱最多 9 个 2*2
if (cnt2 > slots_two) {
boxes += (cnt2 - slots_two + 8) / 9;
}
// 第三步:剩下的空间全归 1*1。
// 总面积减去已被占用的面积(6*6 用 36、5*5 用 25、4*4 用 16、3*3 用 9、2*2 用 4),
// 就是所有包裹加起来还能放下的 1*1 个数。
ll slots_one = 36 * boxes - 36 * cnt6 - 25 * cnt5 - 16 * cnt4 - 9 * cnt3 - 4 * cnt2;
// 放不下 1*1 就继续开新箱,每箱 36 个
if (cnt1 > slots_one) {
boxes += (cnt1 - slots_one + 35) / 36;
}
cout << boxes << "\n";
}
return 0;
}复杂度
- 时间:每个订单只做常数次整数运算(
),设订单数为 ,总时间 。 真实数据里最大一档( boxes8)有 50 个订单、每个型号数量在以内, C++ 与 Python 都在毫秒级完成,限时 1000 ms 绰绰有余。 - 空间:只保存当前订单的六个计数与累计箱数,
。 C++ 的 left_for_two[4]是唯一的常量表。
总结
- 高度相同的三维装箱退化成二维方格切割,这是本题的第一个关键观察;
- 由大到小逐层处理:
各占一箱 → 每四箱一个 → 查表补 → 总面积补 , 每层都取到硬下界,因此贪心正确; - 唯一需要"记下来"的细节是
余数对应的 槽位数 ,其余全部由公式直接给出; - 两个坑:
那一层要用更新后的 boxes重算总面积;输入以六个结束, 该行不输出。