案例 043 同一家店,出餐有先后:等齐一起走还是滚动取送

案例 043 同一家店,出餐有先后:等齐一起走还是滚动取送

摘要:一家店 7 单、出餐间隔 2.0 min,骑手面临"等齐一起走"与"滚动取送"的选择。本文建立带出餐释放时刻的单车多趟取送 MILP:\(x_{i,k}\) 定趟次、\(y_{ij,k}\) 定趟内弧、\(s_k\ge r_i\) 锁住"取不早于出餐",目标为最小化完工时间;等齐是 \(K=1\) 的特例,两条策略在同一模型内可比。Gurobi 求得最优滚动方案为 2 趟(先送最早出餐的 2 单),完工 33.4 min、里程 10.15 km、1 单超时;等齐为 40.3 min、8.28 km、2 单超时,字面的逐单往返最差(70.7 min)。间隔扫描给出临界出餐间隔 0.53 min(32 秒):小于它等齐不亏,大于它滚动占优;877 种分趟方案的精确枚举校验了 MILP 并给出完工—里程前沿。

关键词:即时配送;出餐时序;多趟取送;释放时刻;Gurobi

1 问题提出

1.1 问题是什么

12:00 整,一家肠粉店同时挂了 7 张单,但出餐不是同时的:后厨先蒸后煎再煮汤,7 个餐按 2.0 min 的间隔陆续出餐,从 0.5 min 排到 12.5 min。骑手 12:00 就在门口等着,手里只有一个人、一辆车、一个保温箱。

这就产生了一个二选一的决策:

  • 等齐一起走:站在门口等到 12.5 min,7 个餐全齐,装一箱,按一条最优路线一次送完;
  • 滚动取送:出几个送几个,送完回店再取下一批(题面说的"取一个送一个")。

不是"谁快谁慢"的直觉题:等齐省里程但白等 12.0 min;滚动早上路但要多跑几趟回程。7 单的取餐序是固定的(出餐先后不可改),可送达序可以随便排——这正是本案例的关键:取餐序与送达序解耦。

表 1 七单数据(出餐时刻按 2.0 min 间隔;时限 = 32 min ± 1 min)

订单 送达点 方位 / 度 距离 / km 份数 出餐 / min 时限 / min
A 冼村路口 15 1.2 2 0.5 30
B 体育东路 35 0.8 1 2.5 31
C 珠江新城 55 2.0 2 4.5 32
D 猎德大道 80 2.4 3 6.5 33
E 员村二横路 105 1.6 1 8.5 32
F 车陂南 130 2.2 2 10.5 31
G 棠下村口 160 1.0 1 12.5 33

7 个点都落在门店东北方向的一个扇形里(15°—160°),最远 2.4 km。这个几何很关键:串成一趟只要 8.28 km、28.8 min;但每多跑一趟回程,就要多付一次"从最远端骑回店"的空驶。

图 1 滚动取送的两趟路线(括号内为该单出餐时刻)

1.2 为什么要优化

  1. 等齐的等待是硬成本:等到 12.5 min 才出发,等于把 7 单的"最早可送时刻"全部推迟到最后一单出餐,完工时间被这一等直接顶到 40.3 min,2 单超时。
  2. 滚动不是免费的:趟数从 1 增到 2,里程从 8.28 km 涨到 10.15 km(+1.87 km,23%),多付 1 次回程空驶。是否划算取决于等待省下的时间与多跑一趟花掉的时间谁大。
  3. "取一个送一个"字面执行最差:逐单往返完工 70.7 min、里程 22.4 km,是三者中最差的——滚动 ≠ 逐单往返,滚动的正确形态是"按出餐节奏分趟"。

图 2 出餐时刻—距离全景(气泡=份数)

1.3 Gurobi 如何求解

建模→分趟 MILP→精确校验→间隔扫描。模型用"趟"作为基本单元:\(x_{i,k}\) 表示订单 \(i\) 归第 \(k\) 趟,\(y_{i,j,k}\) 是趟内弧,\(s_k\) 是第 \(k\) 趟出门时刻。三条时间链把"出餐—取餐—送达—回店—下一趟出发"串起来,目标是最小化末单完成时刻。由于弧上时间严格为正,时间递推天然排除子回路。MILP 解出后,又用 Held-Karp 动态规划把任意子集的最短巡回预先算好,再对 \(2^{7}\) 个子集做分趟动态规划(等价于全枚举 877 种分趟方案),既校验 MILP 的完工时间,又给图 8、图 9 提供完整解空间。

2 数学模型

2.1 符号与参数

订单 \(i\in\{1,\dots,7\}\),趟次 \(k\in\{1,\dots,K\}\)(\(K=7\)),\(0\) 为门店。\(r_i\) 出餐时刻,\(D_i\) 承诺时限,\(d_{ij}\) 距离,\(\tau_{ij}=3.0\,d_{ij}\) 行驶分钟(20 km/h),\(\sigma=1.0\) min 停靠交付。\(x_{i,k}\)、\(y_{ij,k}\)、\(u_k\) 为 0-1 变量,\(a_i\) 送达时刻,\(s_k\) 出门时刻,\(e_k\) 回店时刻,\(C\) 完工时间。

2.2 目标函数

\[\min\; C+\lambda\sum_{k}\sum_{(i,j)}d_{ij}y_{ij,k} \tag{1} \]

主项是完工时间,\(\lambda=0.02\) 只是极小权重的里程平局打破项(\(\lambda\sum d\le0.20\) min,不影响完工时间的比较)。等齐策略是 \(K=1\) 的特例,所以滚动的完工时间必然不劣于等齐;两者的差全部来自"分趟"这个自由度。

2.3 约束条件

每单恰归一趟,趟次按序启用(消对称):

\[\sum_{k}x_{i,k}=1,\qquad \sum_{i}x_{i,k}\ge u_k,\quad x_{i,k}\le u_k,\quad u_k\ge u_{k+1} \tag{2} \]

趟内流量守恒与门店进出各一次:

\[\sum_{j\ne i}y_{ij,k}=x_{i,k},\qquad \sum_{j\ne i}y_{ji,k}=x_{i,k},\qquad \sum_{j}y_{0j,k}=\sum_{j}y_{j0,k}=u_k \tag{3} \]

取餐不能早于出餐(本案例的核心约束),以及送达时刻递推:

\[s_k\ge r_i-M(1-x_{i,k}) \tag{4} \]

\[a_j\ge s_k+\tau_{0j}-M(1-y_{0j,k}),\qquad a_j\ge a_i+\sigma+\tau_{ij}-M(1-y_{ij,k}) \tag{5} \]

回店与下一趟衔接(同一辆车,不能时空重叠):

\[e_k\ge a_i+\sigma+\tau_{i0}-M(1-y_{i0,k}),\qquad s_{k+1}\ge e_k,\qquad C\ge a_i+\sigma \tag{6} \]

\[x_{i,k},y_{ij,k},u_k\in\{0,1\},\qquad a_i,s_k,e_k,C\ge 0 \tag{7} \]

\(M=80\) 取"最长回路 + 最长等待"的量级(最坏完工也才 75 min 上下)。约束 (4) 把出餐序写成了硬的下界:\(r_i\) 随出餐序递增,但式 (5) 只约束弧上的先后,不要求送达序与出餐序一致——这是"先后取不等于先后送"的形式化。

规模与性质:0-1 变量 448 个、约束 636 条、非零 2412 个;Gurobi 墙钟 12.36 s、gap 0.0e+00。模型是带出餐释放时刻的单车多趟取送问题:没有容量约束、没有时间窗上界,看似简单,但"分几趟、每趟带谁"是集合划分,\(K=7\) 时组合数 877,规模小到可以精确枚举校验。

3 Gurobi 求解实现

3.1 建模要点

m.addConstr(s[k] >= R[i] - M * (1 - x[i, k]))                    # 出餐才可取
m.addConstr(a[j] >= a[i] + 1.0 + 3.0 * DM[i+1, j+1] - M * (1 - y[i, j, k]))
m.addConstr(s[k+1] >= e[k])                                      # 回店才能再出发
m.setObjective(C + 0.02 * km_total, GRB.MINIMIZE)

两个实现细节。其一,"等齐"不必单独建模:令 \(K=1\) 就是等齐,所以两条策略在同一个模型里可比,差额纯粹来自分趟自由度,避免了"两套代码算两套数"的口径不一致。其二,趟次对称性用 \(u_k\ge u_{k+1}\) 打断:否则同样的分趟方案有 \(K!\) 种排列,分支定界会白跑一个数量级。

3.2 求解结果

表 2 三种策略的指标(MILP 与动态规划结果一致)

策略 趟数 完工 / min 总里程 / km 平均送达 / min 超时单数 超时总量 / min
等齐一起走 1 40.3 8.28 26.8 2 11.4
逐单往返 7 70.7 22.40 35.6 4 84.2
滚动取送 2 33.4 10.15 19.4 1 1.4

最优滚动方案:第 1 趟 2.5 min 出发送 A、B;第 2 趟 12.5 min 出发送 C、D、E、F、G。完工时间 33.4 min,比等齐省 7.0 min(17%),代价是多跑 1.87 km、超时从 2 单降到 1 单。MILP 给出的分趟完全一致(完工 33.39 min、2 趟、里程 10.15 km),两条独立解法互为校验。

图 3 三种策略的完工时间与超时单数

4 结果分析

4.1 结构与过程

图 1 的两趟分工很干净:第 1 趟只带 2 单(A、B,都是最早出餐的两单),2.5 min 出发,在小扇区里绕一小圈就回店;第 2 趟装剩下 5 单,等最后一单出餐后出发。第 1 趟的意义不是"多送几单",而是把"最早出餐的两单"从等待里捞出来——如果这两单也跟着等,它们的送达时刻会被推迟 12.0 min。

图 2 把出餐时刻与距离摊开:先出餐的 A、B 恰好是近单(1.2、0.8 km),第 1 趟的往返只要 2.53 km;后出餐的 C、D、E、F、G 沿扇形向外铺开,串成一趟才划算。"先出的近、后出的远"这个结构正是滚动能赢的原因:先出的单送起来便宜,等的代价却和距离无关。

图 4 的累计阶梯最能说明差别:等齐的曲线在 12.5 min 之前一直贴地,之后在 28 min 里连爬 7 级;滚动的曲线在 8 min 左右就先抬一级(第 1 趟的 2 单),之后与等齐几乎平行。滚动赢的不是斜率,是起跑时刻。

图 4 三种策略的累计送达阶梯(含出餐进度)

4.2 灵敏度:出餐间隔有多大才该滚动

图 5 扫间隔 \(\delta\) 从 0.5 到 5.0 min。\(\delta\le 0.5\) 时两条线重合(省 0 min)——间隔太小时,等齐几乎不用等,分趟纯属多跑;越过 \(\delta\approx0.53\) min 后滚动开始严格占优,\(\delta=1.0\) min 时已省 2.4 min,此后节省随间隔近似线性上升:\(\delta=2.0\) 省 7.0 min,\(\delta=5.0\) 省 12.6 min。临界间隔只有 32 秒,比直觉小得多:7 单时等齐要等 6 个间隔,每个间隔 1 min 就是 6 min 的白等,而第 1 趟的往返只要 5—6 min,两边几乎等量级。

图 6 把间隔与距离尺度一起扫。色块沿横轴变深(间隔越大越该滚动),沿纵轴变浅(单越远,多跑一趟的回程越贵,滚动的收益被吃掉)。\(\delta=0.5\) 那一列全图最浅(节省≈0),距离缩放 1.15 的那一行颜色也偏浅——距离越大、间隔越小,越该等齐。本案星标(2.0 min,尺度 1.0)落在 7.0 min 的节省上,属于"该滚动"的一侧。

图 5 出餐间隔对完工时间与滚动节省的影响

图 6 出餐间隔 × 距离尺度的二维灵敏度(色块=滚动节省 / min)

4.3 第三维与前沿

图 7 把里程与趟数放在同一张图上:等齐的里程是一条水平线 8.28 km(与间隔无关),滚动的里程随间隔阶梯式抬升 10.15→14.95 km,台阶正好对上趟数 2→3→4 的跳变(\(\delta\ge3.5\) min 时跳到 3 趟)。每多一趟,多付的是一整段回程空驶,所以里程是阶梯、不是斜线。

图 8 把 877 种分趟方案全部画在"完工时间—总里程"平面上。前沿是一条明显的双曲带:想早完工就多分趟、多跑里程。阴影是承诺时限 32 min 右侧的区域——等齐(方框)正落在阴影里,它不是"慢一点",而是"末单必然超时"。逐单往返(叉)在右上角,用 22.4 km 的里程换来 70.7 min 的完工,两头都最差,被完全支配。

图 7 出餐间隔对总里程与滚动趟数的影响

图 8 完工时间—总里程前沿(阴影=承诺时限外)

4.4 分布与逐单余量

图 9 是全部 877 种方案的完工时间累计分布:等齐只优于 99% 的方案,滚动优于 100% 的方案。中位方案是 56.0 min,比最优滚动差 22.6 min——滚动的收益不是"运气好挑到一组",而是分趟粒度选对了。

图 10 逐单对比送达时刻与时限的余量。等齐的 F、G 超时(余量 +4.1、+7.3 min),滚动的 C 超时。A、B 两单在滚动下分别提前 10.0、10.0 min 送达——这正是第 1 趟的贡献;而 D、E 这些中间单在两策略下相差不超过 2.8 min(滚动反而稍晚),说明滚动只对"早出餐的单"有效。

图 9 全部 877 种分趟方案的完工时间累计分布

图 10 逐单送达时刻与时限余量(柱顶为余量 / min)

5 结论

  1. 等齐的代价是可算的:等到 12.5 min 出发、一趟 8.28 km,完工 40.3 min,2 单超时。等待时间 \(=(N-1)\delta\),与距离无关,所以出餐越拖,等齐越吃亏。
  2. 滚动的正确形态是分趟,不是逐单往返:最优解是 2 趟(第 1 趟 2.5 min 出发送 A、B;第 2 趟 12.5 min 出发送 C、D、E、F、G),完工 33.4 min;而字面的"取一个送一个"要 70.7 min、22.4 km,被完全支配。
  3. 临界出餐间隔 \(\delta^{*}\approx0.53\) min(32 秒):小于它等齐不亏,大于它滚动开始占优;本案 \(\delta=2.0\) min 已在滚动一侧,省 7.0 min(17%)。判据可写成 \(\delta>(\text{一趟往返}-\text{顺路捎带})/(\text{等待的单数})\),本质是"多跑一趟的时间"与"白等的时间"比大小。
  4. 趟数随间隔阶梯上升:\(\delta\ge3.5\) min 时最优解跳到 3 趟,里程从 10.15 km 抬到 14.95 km。滚动的代价是里程,收益是时间,两者不能同时最优(图 8 的前沿)。
  5. 取餐序与送达序解耦:约束 (4) 只锁"取不早于出餐",不锁"送达先后"。本案最优解里第 1 趟送 A、B,第 2 趟送 C—G,送达序与出餐序一致纯属几何巧合;若把远单排前面出餐,最优送达序会反过来。

参考文献

[1] Gurobi Optimization LLC. Gurobi Optimizer Reference Manual[Z]. 2024.
[2] Kellerer H, Pferschy U, Pisinger D. Knapsack Problems[M]. Berlin: Springer, 2004.
[3] Martello S, Toth P. Knapsack Problems: Algorithms and Computer Implementations[M]. Chichester: John Wiley & Sons, 1990.
[4] 司守奎, 孙玺菁. 数学建模算法与应用[M]. 3 版. 北京: 国防工业出版社, 2021.
[5] Toth P, Vigo D. Vehicle Routing: Problems, Methods, and Applications[M]. 2nd ed. Philadelphia: SIAM, 2014.
[6] Held M, Karp R M. A dynamic programming approach to sequencing problems[J]. Journal of the Society for Industrial and Applied Mathematics, 1962, 10(1): 196-210.

posted @ 2026-11-11 18:01  小青蛙电气  阅读(2)  评论(0)    收藏  举报