遇到不熟悉的词,先看这 3 个解释
打开完整术语小抄 ↗三台机器做一件产品分别需要3秒、2秒、5秒,从时刻0一起开始。订单要7件。等最快那台独自做完需要14秒,三台一起工作显然可能更早,怎样找到第一个已经够数的时刻?
不必把0秒到答案之间的每一秒都列出来。我们先会回答“到第 t 秒一共完成几件”,再用这个判断缩小时间范围。
把例子带到自己的电脑上
下载完整练习包
需要 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。
下载 ZIP · 5 个文件包里有什么
- demo.py:完整可运行源码
- README.md:运行步骤与练习
- observed.stdout.txt:本次实际输出
- verification.json:源码和运行验证记录
- SHA256SUMS.txt:文件摘要
在这个文件夹打开 PowerShell,逐行运行
python --version
python -B demo.py解压后,在含 demo.py 的目录打开终端。命令分别回车执行。独立机器从0同时连续开工,固定正整数秒耗时;780份机器清单、16380个目标对逐秒倒计时,另含2组大整数、12组顺序变化和14类拒绝输入;无真实排产或性能测量。
找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。
先判断某个时刻够不够,再找最早的可行时刻
正在准备实验控件…
三台机器从 t=0 同时开始,各自每完成一件才计数;耗时为正整数,没有启动成本、停机或共享资源限制。页面只取 1–10 秒和 0–30 件,整数计算可精确表示;Python 下载版另外核对大整数和非法配置。
先固定生产规则,数字才有明确含义
本模型中所有机器从时刻0开始,各自连续生产;一台做完一件后立即开始下一件。单件耗时是固定的正整数秒,第一件在该耗时结束时才完成。机器之间没有共享工位、抢原料或相互等待,也没有启动准备、停机和换型时间。
这些条件让各台产量可以直接相加。我们求订单数量至少达标的最早时刻;未完成的一件不计入产量。让机器主动闲置不会更早达标,因此连续开工给出本模型中最快能完成的数量。
函数接收非空的 list 或 tuple,每项必须是正整数,目标必须是非负整数;布尔值不作为数量或秒数。没有可用机器、耗时为零或负数、浮点耗时等输入都会明确拒绝。先验证配置,再处理目标0,避免错误配置因目标为0而被悄悄接受。
第 t 秒完成的件数,为什么要向下取整
一台每 k 秒完成一件,完成时刻就是 k、2k、3k……。到第 t 秒已经完成几件,就是 t//k。比如一件需3秒,到第7秒只完成2件,第三件还差2秒;不能把7/3得到的部分件数算成完成。
所以总产量为各台 t//k 的和。第7秒,三台分别完成[2,3,1],总共6件;第8秒分别完成[2,4,1],总共7件。这两个相邻时刻已经说明答案是8秒。
全部变量使用整数,代码直接写 //。先做 / 转成浮点,再转回 int,会把大整数问题变成浮点精度问题;而且我们需要的数学含义本来就是完整件数。
产量会停一会儿,也会一下跳过目标
时间增加时,完成的产品不会减少。因此“总产量至少达到目标”的判断,只会从不满足变成满足,之后保持满足。二分依赖的是这个真假顺序,不要求机器耗时列表先排序,也不要求每一秒产量都增加。
两台机器都每2秒完成一件,产量会从0跳到2,再跳到4。目标若是3件,永远不会遇到总量恰好等于3的时刻;但第4秒已经够用。因此判定必须写 completed >= target,不能只接受等号。
这里搜索的对象是整数时间,由计算规则隐式给出产量。我们没有创建一个包含所有时刻的数组;每次只计算正在询问的那一秒。
左边确定不够,右边确定够用
正目标下,lo=0 一定不够,因为还没有完成任何产品。令 hi=最快单件耗时×目标;就算只有最快机器开工,到这个时刻也能做够,其他机器只会增加数量,所以 hi 一定够用。
循环始终保持 lo 不可行、hi 可行。取中点 mid:如果够用,就把 hi 改为 mid,让它继续作为可能的最早答案;如果不够,就把 lo 改为 mid,因为更早的时刻也都不够。
当 hi−lo=1,两端之间没有别的整数时刻了,返回 hi。目标0在验证输入后直接返回0,不进入这个需要“lo一定不够”的循环。最早完成时刻也是整数,因为本模型所有机器的完成事件都发生在整数秒。
七件产品的三次询问,把区间缩成7与8
三台耗时[3,2,5],目标7,初始两端为0与14。先问第7秒,产量6件不够,所以 lo=7;现在答案仍在7秒之后、14秒以内。
再问第10秒,产量为3+5+2=10件,已经够用,于是 hi=10。然后问第8秒,产量为2+4+1=7件,也够用,于是 hi=8。此时 lo=7、hi=8,相邻两端一边不够、一边够用,答案确定。
边界定义和更新公式是一套约定。若把可行的 mid 随手改成 hi=mid−1,却仍声称 hi 始终可行,就可能把真正答案排除或破坏证明。修改循环写法时,要重新检查每个边界代表什么。
让每台机器逐秒倒计时,作为另一份核对
保存完整源码为 demo.py,执行 python demo.py。2026-10-23 在 Windows、CPython 3.14.0 实跑,只用标准库。输出包含默认二分轨迹、两台同速机器跳过目标的例子、目标0和拒绝输入统计。
ticking_oracle 给每台机器一个剩余秒数,每过一秒减一,减到零就记一件并重新装入该机器的单件耗时。它逐秒模拟,不使用二分,也不调用 t//k 的产量公式,适合在小输入上独立核对最早完成时刻。
程序枚举1到4台机器、每台耗时1到5秒的780份清单,对目标0到20逐个检查,共16,380个结果与倒计时一致。另检查两组大整数、12组机器顺序变化和14类非法输入;错误输入在进入搜索前拒绝,检查使用显式 AssertionError。
展开收起代码 · 小实验
长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。
"""Independent machines start at t=0; positive integer seconds per item."""
from itertools import product
def require(condition, message):
if not condition:
raise AssertionError(message)
def min_time(periods, target, emit=None):
"""Earliest integer time when total completed items >= target.
periods: nonempty list/tuple of positive int values, excluding bool.
target: nonnegative int, excluding bool. Validate even when target is zero.
Inputs are not changed; observers must not mutate periods.
"""
if not isinstance(periods, (list, tuple)) or not periods:
raise ValueError("a nonempty list or tuple of machine periods is required")
if any(type(k) is not int or k <= 0 for k in periods):
raise ValueError("each period must be a positive int, not bool")
if type(target) is not int or target < 0:
raise ValueError("target must be a nonnegative int, not bool")
if target == 0:
return 0
lo = 0 # Infeasible: positive target, zero completed items.
hi = min(periods) * target # Feasible: the fastest machine alone suffices.
while hi - lo > 1:
mid = lo + (hi - lo) // 2
completed = sum(mid // k for k in periods)
if emit is not None:
emit(lo, hi, mid, completed)
if completed >= target:
hi = mid # This works, but an earlier time may also work.
else:
lo = mid # This and all earlier times cannot work.
return hi
def ticking_oracle(periods, target):
"""Small-test reference: advance each machine's countdown one second.
No binary search and no time // period production formula are used here.
Only called with known-valid small inputs.
"""
remaining = list(periods)
completed = seconds = 0
while completed < target:
seconds += 1
for index, period in enumerate(periods):
remaining[index] -= 1
if remaining[index] == 0:
completed += 1
remaining[index] = period
return seconds
def main():
periods, target = [3, 2, 5], 7
trace = []
answer = min_time(periods, target, lambda lo, hi, mid, count: trace.append((lo, hi, mid, count)))
require(answer == 8 and ticking_oracle(periods, target) == 8, "official model example")
print(f"periods={periods}; target={target}; guaranteed_upper={min(periods) * target}")
for lo, hi, mid, count in trace:
print(f"lo={lo}; hi={hi}; mid={mid}; completed={count}; feasible={count >= target}")
print(f"answer={answer}; at_7={[7 // k for k in periods]}; at_8={[8 // k for k in periods]}")
jump = min_time([2, 2], 3)
require(jump == 4, "must accept >= target when count jumps over it")
require(min_time([3, 2, 5], 0) == 0, "zero items")
print(f"jump_periods=[2, 2]; target=3; answer={jump}; completed=4; zero_target=0")
machine_lists = cases = 0
for n in range(1, 5):
for periods in product(range(1, 6), repeat=n):
machine_lists += 1
previous = -1
for target in range(21):
answer = min_time(periods, target)
wanted = ticking_oracle(periods, target)
require(answer == wanted, f"oracle mismatch: {periods}, {target}")
require(answer >= previous, "more requested items finished earlier")
previous = answer
cases += 1
require(machine_lists == 780 and cases == 16380, "enumeration coverage")
large_target = 10**40
require(min_time([7], large_target) == 7 * large_target, "large single-machine integer")
period, target = 10**30 + 7, 2 * 10**20 + 1
require(min_time([period, period], target) == period * ((target + 1) // 2), "large simultaneous events")
order_cases = 0
for periods in ([3, 2, 5], [1, 5, 3, 2], [5, 5, 2, 1]):
before = list(periods)
for target in (0, 1, 7, 20):
require(min_time(periods, target) == min_time(periods[::-1], target), "machine order matters")
order_cases += 1
require(periods == before, "input mutated")
invalid = [([], 0), ([], 1), ([0], 1), ([-2], 1), ([1.5], 1), ([True], 1),
(None, 1), ("23", 1), (iter([2, 3]), 1), ([[2]], 1),
([2], -1), ([2], True), ([2], 1.0), ([2], "7")]
rejected = 0
for periods, target in invalid:
try:
min_time(periods, target)
except ValueError:
rejected += 1
else:
raise AssertionError("invalid input accepted")
print(f"machine_lists={machine_lists}; target_cases={cases}; huge_integer_cases=2; order_cases={order_cases}")
print(f"rejected_inputs={rejected}; validates_configuration_before_zero_target=True")
print("PASS: minimum times match ticking oracle; model has no startup/shared-resource limits")
if __name__ == "__main__":
main()
怎样运行:在独立练习文件夹中,依次执行下载区的命令。2026-10-23 使用 Python 3.10 或更新版本,无第三方依赖;实测 Windows / Python 3.14.0,Python 3.10 仅做语法兼容检查 复跑这份源码并核对完整输出。独立机器从0同时连续开工,固定正整数秒耗时;780份机器清单、16380个目标对逐秒倒计时,另含2组大整数、12组顺序变化和14类拒绝输入;无真实排产或性能测量。
运行后对照结果
periods=[3, 2, 5]; target=7; guaranteed_upper=14
lo=0; hi=14; mid=7; completed=6; feasible=False
lo=7; hi=14; mid=10; completed=10; feasible=True
lo=7; hi=10; mid=8; completed=7; feasible=True
answer=8; at_7=[2, 3, 1]; at_8=[2, 4, 1]
jump_periods=[2, 2]; target=3; answer=4; completed=4; zero_target=0
machine_lists=780; target_cases=16380; huge_integer_cases=2; order_cases=12
rejected_inputs=14; validates_configuration_before_zero_target=True
PASS: minimum times match ticking oracle; model has no startup/shared-resource limits
一次判定遍历机器,搜索只询问对数级的时刻
设机器数为 n,初始可行上界为 U。一次产量计算遍历 n 台机器;每轮把时间范围大约减半,正目标的总成本按整数操作次数计为 O(n log(U+1)),包含最初的输入检查。核心只保存固定数量的计数与边界;若把全部轨迹保存下来,显示所需空间另外计。
Python 整数可以扩展位数,但大整数乘除并不是固定成本;上述写法统计的是操作次数,没有声称任意长度整数都在相同时间内完成。移植到固定宽度语言时,还要检查上界乘法、总产量累加与中点计算的溢出。
本模型没有测量真实车间吞吐,也没有求共享资源下的排产。若机器有启动时间、停机、原料限制或彼此等待,要先重新建立产量与可行性判断,并确认它仍符合要搜索的边界;只保留二分循环,不能自动解决改变后的业务问题。
记住这些要点
- 每台完成件数是 t/
/k,总量按独立机器相加。 - 目标是至少够用,产量可以直接跳过目标。
- 始终保持 lo 不可行、hi 可行,相邻时返回 hi。
- 目标0先单独处理;模型规则和每次判定的成本都要写清。
别把例子用过头
- 只接受产量恰好等于目标,漏掉同时完成多件的时刻。
- 错误地要求机器耗时先排序,忽略真正单调的是时间上的可行判断。
- 没有已证明可行的上界,或混用另一种二分的边界更新。
- 允许0秒耗时导致除零,或把模型估算当作真实排产测量。
自己试一小步
不展开所有时间,怎样证明你找到的那个时刻确实是最早?
- 自选三台正整数耗时,目标不要刚好被每次产量增长整齐命中。
- 手写一个确定不够与一个确定够用的时刻,再逐轮列出lo、hi、mid和产量。
- 对返回时刻及前一秒分别核对;另测目标0、单台机器与两台同速机器。
- 把模型改为“其中一台启动前要等待”,只写出原判定哪里需要改变,不先假定原程序仍然正确。
做到这些,就算完成
- 每轮都能解释左右边界的真假含义。
- 至少一例产量跨过目标,仍返回最早够用的时刻。
- 独立核对方法不直接调用同一个二分或产量计算函数。
查原始资料
想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。
- CSES:Factory Machines (新标签页)↗
独立机器并行制造产品的任务定义,以及单件耗时3、2、5秒、目标7件时答案为8秒的例子。
- Competitive Programmer's Handbook:Finding the smallest solution (新标签页)↗
可行判断只发生一次真假转折时,可以搜索最早可行位置;判定代价应计入总成本。
- Python 3.14:整数整除 (新标签页)↗
整数//得到向下取整的整数商,避免先转浮点再取整。