第 15 题
答案与解析
第(1)小题
206
09:15时,编号205的物品尚未送达,其余图示物品均已到达。按编号连续性划分,可得到两个序列:
| 连续编号序列 | 长度 | 是否能满足运走3个物品 |
|---|---|---|
| 201~204 | 4 | 能 |
| 206~208 | 3 | 能 |
应在长度不少于3的序列中选择最短的序列,因此选择206~208,从其起始编号206开始运走。
第(2)①小题
True
程序分析
def check(reqs, m):
i=j=0
s=0
while j < len(reqs):
if reqs[j][0] - reqs[i][0] < 60:
...
else:
...
return Falsereqs已按时间升序排列,两个下标i、j均从0开始,s初值为0。循环根据reqs[j]与reqs[i]的时间差,决定纳入新的需求还是移除较早的需求;j达到列表长度且未提前返回时,函数返回False。
if reqs[j][0] - reqs[i][0] < 60:
s += reqs[j][1]
if s>m:
return True
j+= 1时间差小于60时,将第j条需求的数量加入s;若累计数量超过m,立即返回True,否则j加1,继续考察下一条需求。因此j指向下一条待纳入的需求,s用于累计当前时段内的需求数量。判断使用严格小于60,时间相差恰好60分钟的两条需求不会同时计入该时段。
else:
s -= reqs[i][1]
i+= 1时间差达到或超过60时,先从s中减去第i条需求的数量,再将i加1,移除当前最早的一条需求。j保持不变,下一轮仍考察同一条待纳入需求,必要时继续移除较早记录。由此,i是当前已纳入区间的左端;每轮开始时,s对应下标i至j-1的数量总和,i=j时该区间为空。
按程序的纳入、移除顺序计算:
| 操作 | 操作后的有效需求时间(分钟) | s | 是否返回 |
|---|---|---|---|
| 纳入时间6的需求 | 6 | 1 | 不返回 |
| 纳入时间19的需求 | 6,19 | 14 | 不返回 |
| 纳入时间35的需求 | 6,19,35 | 17 | 不返回 |
| 考察时间70,因70-6=64,移除时间6的需求 | 19,35 | 16 | 不返回 |
| 因70-19=51,纳入时间70的需求 | 19,35,70 | 18 | 不返回 |
| 因75-19=56,纳入时间75的需求 | 19,35,70,75 | 25 | 25>20,返回True |
时间19至75的需求处于同一个连续60分钟范围内,数量总和为13+3+2+7=25,超过m=20。
第(2)②小题
199
共用程序分析见第(2)①小题。
函数返回False,说明没有在s>m处提前返回,而是j增加到100后结束循环。每轮循环只会执行以下一种操作:
- 纳入需求,使j加1;
- 移除需求,使i加1。
因此,j加1恰好执行100次。i不会超过j:当i=j时,两条记录实际是同一条,时间差为0,必定进入使j加1的分支。最后一次循环开始时j最多为99,所以i最多从0增加到99,即执行99次移除。循环体执行次数至多为100+99=199。
这个上限可以达到。例如第r条需求(r从0到99)的时间为60r分钟、数量为1,取m=1。相邻需求恰好相差60分钟,每纳入后一条之前,都必须先移除前一条;累计数量始终不超过1,最终返回False。共纳入100次、移除99次,循环体执行199次。
第(3)小题
idx += 1
num >= need and num < bestnum
lst[p][0] += need
程序分析
res = proc(reqs, items)主程序将按送达时间排列的items和按需求时间排列的reqs传给proc。items每项保存“送达时间、物品编号”,reqs每项保存“运走时间、需求数量”,res接收处理结果。
def proc(reqs,items):
res=[]
lst=[[-1,-1,-1]] #初始化为仅包含头节点的链表
idx=0
for i in range(len(reqs)):
cur,need=reqs[i][0],reqs[i][1]
...
return res按addpid的说明,lst用列表模拟链表,每个数据结点保存一个连续编号区间:[起始编号,终止编号,后继结点下标]。lst[0]是辅助头结点,不代表物品序列;lst[0][2]保存首个数据结点的下标,-1表示没有后继。初始链表只有辅助头结点,库存为空。
有效结点沿链接按起始编号升序排列,不能按lst中的存储下标判断先后。程序用res记录成功处理的需求,并按需求时间依次取出cur和need;每轮先接收已到达的物品,再查找序列并更新库存。
while idx<len(items)and items[idx][0]<cur:
pid=items[idx][1]
addpid(pid,lst) #根据新加入的物品编号pid更新链表
①idx用于访问下一件尚未加入库存的物品。只要该物品的送达时间严格早于cur,就取出编号pid并调用addpid,将它并入现有连续编号序列;必要时合并相邻区间。空①负责推进到后续物品。本程序使用items[idx][0]<cur,因此与需求时刻相同的物品不在本轮加入。
bestnum=len(items)+1
bestpre,pre=-1,0
p=lst[0][2]
while p!=-1 and need!=bestnum:
num=lst[p][1]-lst[p][0]+1
if ②:
bestnum=num
bestpre=pre
pre=p
p=lst[p][2]bestnum初值为总物品数加1,大于任何现有序列长度。pre初值为辅助头结点下标0,p初值为首个数据结点的下标。每轮处理当前结点后,在循环末先执行pre=p,再令p移向后继。因此,只要下一轮进入循环体,pre保存的就是当前结点p的前驱下标。
num由“终止编号-起始编号+1”得到,表示当前序列长度。空②条件成立时,用num更新候选长度bestnum,并把候选结点的前驱下标保存到bestpre。保留前驱便于后续删除选中结点;bestpre仍为-1则表示没有找到可用序列。循环还允许在候选长度恰好等于need时提前结束。
if bestpre !=-1:
p=lst[bestpre][2]
res.append([i,lst[p][0],lst[p][0]+need-1]) #为res追加一个元素
if bestnum==need:
lst[bestpre][2]=lst[p][2]
else:
③
else:
#取消本次需求,代码略找到候选后,lst[bestpre][2]给出选中结点的下标p。res追加需求下标i和实际运走区间,终止编号为起始编号加need减1。
若bestnum==need,整个序列都被运走,令前驱结点直接链接到p的后继,即从有效链表中删除p;lst中的原数据可以保留,但已不属于有效库存。否则该序列仍有剩余物品,由空③更新结点内容。若没有找到候选,则取消本次需求,不记录运走区间。
填空分析
①
当前items[idx]已由addpid加入库存,接下来应处理下一件物品,故填写:
idx += 1
若idx不变,while会反复处理同一件物品,无法推进到下一件或结束当前接收过程。idx在各次需求之间保留,使已经加入过的物品不会再次加入。
②
候选必须同时满足:长度num不少于need,足以完成本次运输;长度num严格小于目前记录的bestnum,以选择可用序列中最短的一个。因此填写:
num >= need and num < bestnum
链表按起始编号升序遍历,长度相等时先遇到的序列起始编号更小,所以不能用num <= bestnum替换后一个比较,否则会被后遇到、起始编号更大的等长序列覆盖。若找到长度恰好为need的序列,已经达到可用长度的下限;后续等长序列的起始编号也不会更小,故已有循环条件可安全提前停止。
③
进入else时,选中序列的长度大于need。设原起始编号为L,已运走L至L+need-1,剩余序列从L+need开始,终止编号和后继链接均不变。因此只需更新起始编号:
lst[p][0] += need
例如原序列为201~204、need为3时,运走201~203后,应将该结点保留为204~204。
