案例 044 客户说「20:00 以后送」
案例 044 客户说「20:00 以后送」
摘要:9 单晚高峰配送中,第 9 单客户要求 20:00 以后送达,早到会被拒收。把这条约束的左边界做成硬约束、右边界做成软约束,用 Gurobi 建立带时间递推与等待变量的车辆路径混合整数模型,对比「禁止早到」「早到按等待计价」「早到即拒收」三种建模。结果显示:最短路线下骑手在 36.6 min(19:36.6)就抵达客户楼下,早到 23.4 min;硬左窗若不计等待,账面只多花 1.97 元,却有 8.20 元的闲置被挪出账外;早到罚金超过 0.51 元/min(约 31 元/h)时,弃单反而更划算;计入拒收概率后期望成本由 8.20 元升到 9.84 元,结论翻转。
关键词:硬软时间窗;早到等待;拒收风险;混合整数规划;罚金临界值
1 问题提出
1.1 问题是什么
晚高峰 19:00 从体育西路出发,车上有 9 单。前 8 单是平台承诺单(19:28 前送达),第 9 单是南洲花园的预约单,客户留言「20:00 以后送,早到家里没人」。于是这一单的时间窗是 \([60,\,90]\):左边界 20:00 是硬的(早到即被拒收),右边界 20:30 是软的(晚到只扣分)。
表 1 订单数据(时刻以 19:00 为 0,单位 min)
| 订单 | 送达点 | 距店 km | 份数 | 类型 | 时间窗 |
|---|---|---|---|---|---|
| 01 | 天河城 | 0.7 | 1 | 普通 | 19:28 |
| 02 | 珠江新城 | 1.3 | 2 | 普通 | 19:28 |
| 03 | 猎德大道 | 1.9 | 2 | 普通 | 19:28 |
| 04 | 冼村路 | 0.9 | 1 | 普通 | 19:28 |
| 05 | 员村二横 | 2.2 | 2 | 普通 | 19:28 |
| 06 | 车陂南 | 2.5 | 1 | 普通 | 19:28 |
| 07 | 棠下村口 | 1.4 | 2 | 普通 | 19:28 |
| 08 | 岗顶 | 0.8 | 1 | 普通 | 19:28 |
| 09 | 南洲花园 | 1.5 | 1 | 预约 | 20:00–20:30 |
最短路线把 8 单串成一圈再折向第 9 单,里程 11.03 km,到第 9 单楼下才 36.6 min。也就是说,无论怎么排,这一单都要早到 23.4 min——早到不是路线没排好,而是 8 单送得太快。
1.2 为什么要优化
第一,早到不是"多等一会儿"这么简单:客户家里没人,敲门没人应,订单只能挂在门口或被拒收,拒收意味着收入归零还倒扣。
第二,等待时间是真金白银。骑手在楼下等的 23.4 min 里接不了别的单,按 21 元/h 折合 8.20 元,比这一单的里程成本还高。
第三,也是最容易出错的地方:如果模型里放了等待变量却不给它定价,早到惩罚恒等于 0,求解器会毫不犹豫地让骑手在楼下干等,报出一个"零成本"的假最优解。
1.3 Gurobi 如何求解
建模上是一条带时间递推的单车路径问题:二进制 \(x_{ij}\) 决定弧,连续变量 \(t_i\) 记录到达时刻,\(s_{ij}\) 记录每条弧上的磨蹭时间。左边界用 \(t_9 \ge 60\) 硬约束(方案 A)或早到量 \(e_9\) 软约束(方案 B);方案 C 用 0-1 变量 \(u\) 表示这单要不要接。预处理把 10 个节点的弧压缩到 90 条,分支定界在 0.1 s 内收敛,再用三次单参数扫描分别扰动早到罚金 \(p\)、超时罚金 \(\lambda\)、客户距离 \(d_9\),最后做二维网格定位临界面。
2 数学模型
2.1 符号说明
\(i,j\in\{0,1,\dots,10\}\) 为节点(0 与 10 是同一个驻点,分别作起点与终点),\(c_i\) 为停靠交付时间,\(\tau_{ij}\) 为骑行时间,\(d_i\) 为承诺时刻,\(r_9=60\) 为预约下界。决策变量为弧 \(x_{ij}\in\{0,1\}\)、到达时刻 \(t_i\ge 0\)、磨蹭 \(s_{ij}\ge 0\)、早到量 \(e_9\ge 0\)、接单指示 \(u\in\{0,1\}\)。
2.2 目标函数
三种方案共用同一个骨架,差别只在等待项与拒收项:
其中 \(\rho_d=1.20\) 元/km 为里程成本,\(\lambda=0.8\) 元/min 为超时罚,\(p\) 为早到等待罚金,\(L_F=14.0\) 元为弃单损失(收入 12.0 元加平台罚 2 元)。方案 A 取 \(p=0\) 且强制 \(e_9=0\);方案 B 取 \(p=0.35\);方案 C 取 \(p=0.35\) 但令 \(\sum s_{ij}=0\)(不允许在楼下磨蹭)。
2.3 约束条件
时间递推沿弧传播,\(M\) 取候选弧规模的 4 倍:
"早到"由到达时刻与预约下界之差定义,只有接了这单才算:
超时量对每单独立定义,右边界软在这里落地:
流平衡保证每单被访问一次,且第 9 单的访问次数等于 \(u\);\(s_{ij}\le M x_{ij}\) 保证磨蹭只发生在走过的弧上。方案 A 额外加 \(t_9\ge r_9\),方案 C 额外加 \(s_{ij}=0\)。
2.4 规模与性质
10 节点、90 条候选弧、11 个连续时间变量、1 个 0-1 变量,模型是带时间窗的混合整数线性规划。由于时间沿弧严格递增,\(t\) 本身即可消除子回路,无需额外 MTZ 约束。松弛后是 LP,但其时间窗结构使 LP 界很松,必须靠分支定界。
3 Gurobi 求解实现
3.1 建模要点
关键的一条:等待必须显式进目标。代码里把等待拆成两处——弧上的磨蹭 \(s_{ij}\) 与客户楼下的早到 \(e_9\),合并成同一个 idle 表达式乘以 \(p\) 再加进目标。若只写 e9 >= 60 - t9 而不把 \(e_9\) 乘进目标,求解器会把所有空闲塞进最后一跳,早到惩罚恒为 0,得到"免费等待"的假解。
idle = gp.quicksum(s[i, j] for (i, j) in xs) + e9
obj = CD * mil + lam * late + p * idle # 方案 B:等待显式计价
第二个细节是方案 C 的实现:把 \(s_{ij}\) 全部固定为 0 之后,\(t_9\) 只能等于纯作业时间 36.6 min,硬约束 \(t_9\ge 60\) 立刻把 \(u\) 压到 0——求解器自己"决定"弃单,而不是我们人为写死。
3.2 求解结果
表 2 三种建模的最优解对比(增量口径,相对"只送前 8 单")
| 方案 | 里程+超时 / 元 | 隐没闲置 / 元 | 等待或弃单 / 元 | 合计 / 元 |
|---|---|---|---|---|
| A 硬左窗(不计等待) | 13.23 | 8.20 | 0.00 | 15.60 |
| B 早到显式计价 | 13.23 | 0.00 | 8.20 | 23.80 |
| C 早到即拒收 | 11.26 | 0.00 | 14.00 | 27.63 |
方案 A 的账面增量只有 1.97 元,但它在目标里根本没有等待项,23.4 min 的闲置被挪到了账外;按 21 元/h 折算,真实增量是 1.97 + 8.20 元。方案 B 把这笔钱摆到台面上,成本立刻翻了几倍。方案 C 更干脆:既然不能等、早到就拒收,这单只能不接,直接损失 14.0 元。
4 结果分析

图 1 最优序的时序甘特(斜纹为早到等待)
4.1 时序结构 图 1 是最优序 08 → 01 → 02 → 03 → 04 → 05 → 06 → 07 → 09 的甘特。前 8 单在 19:31 前全部送完,骑行段被停靠交付切成小段,节奏很紧;最后一段斜插到南洲花园,36.6 min 抵达,剩下的 23.4 min 只能用斜纹的"早到等待"填满。注意这段等待的宽度和里程成本不在一个量级——它不是路线问题,而是时间窗结构造成的硬缺口。

图 2 距离—到达时刻散点(气泡为份数)
4.2 数据全景 图 2 把所有单摆在"距离—到达时刻"平面上:8 个普通单贴着 19:40 承诺线以下,第 9 单孤零零地悬在 36.6 min,距离承诺下界还有 23.4 min 的垂直落差。气泡大小是份数,第 9 单只有 1 份却占了最大的时间缺口,说明这份缺口与货量无关,只与预约时刻有关。

图 3 三种建模的增量成本分解
4.3 三方案对比 图 3 把增量成本拆成四块。方案 A 的柱子最短,但灰色斜纹那段"隐没的闲置"没有任何一项模型成本与之对应;方案 B 用实心橙色把同一段时间明码标价;方案 C 的柱子几乎全红——弃单损失 14.0 元,比方案 B 还贵 27.63 元之差。三条柱子的高度顺序(A < B < C)说明:把等待定价,方案 B 反而最省钱;拒收规则把 8.20 元的问题放大成 14.0 元的损失。

图 4 累计作业时间与累计里程
4.4 累计过程 图 4 的阶梯显示累计作业时间与累计里程。前 8 单每多送一单,两条阶梯同步抬升;到第 9 单时,里程几乎不涨(增量 1.64 km),时间却要再跳一大截——跳的这部分正是等待。累计里程曲线的末端斜率骤降,是"最后一单没有回头客"的典型形状。

图 5 早到罚金灵敏度与接单决策
4.5 主灵敏度:早到罚金临界值 图 5 让 \(p\) 从 0.05 扫到 1.00。等待成本随 \(p\) 线性上升,与弃单损失 14.0 元的水平线交于 \(p^\ast=0.51\) 元/min。这个临界值有闭式:
即"弃单能省下的钱"除以"必须等的时间"。\(p^\ast=0.51\) 元/min 折合 31 元/h,只有骑手时薪的 1.5 倍左右——平台只要把早到的等待补偿提到这个价位,理性骑手就会开始挑单、弃单。图 5 右轴的阶梯在 \(p^\ast\) 处从 1 掉到 0,正是这个翻转。

图 6 早到罚金 × 超时罚金的成本热图
4.6 二维灵敏度 图 6 把早到罚金 \(p\) 与超时罚金 \(\lambda\) 铺成 4×5 网格。左下角(两个罚金都小)成本最低,右上角最贵;本案参数(星标)落在网格的左中位置,成本 23.80 元左右。等高线在 \(p\) 方向更密,说明成本对早到罚金的弹性大于对超时罚金的弹性——因为第 9 单的 23.4 min 缺口是结构性的,而前 8 单的超时只有 3.0 min。

图 7 第 9 单距离的净收益与闲置时长
4.7 第三维:客户距离 图 7 把第 9 单的距离从 1.2 km 拉到 4.8 km。距离每多 1 km,净收益掉约 1.4 元(里程 + 骑行时间挤占等待时间),曲线在 $$d_9\approx 4.4$$ km 处跌破弃单线:超过这个距离,接单就是亏的。右轴的闲置时长同步缩短,因为路上花的时间正好填掉了缺口——距离越远,早到越少,这也解释了为什么远单的"早到罚金"反而不敏感。

图 8 等待—超时前沿与弃单区
4.8 前沿与弃单区 图 8 把 20 个网格点的(等待成本, 超时成本)画在平面上,非支配点连成前沿,从左下角的"零等待零超时"(理想点)向右上展开。前沿上方的阴影是弃单区:一旦总成本高过 14.0 元,接单就不如放弃。本案点(\(p=0.35,\lambda=0.8\))紧贴前沿下端,说明还有一点余量,但余量很薄。

图 9 相对承诺时刻的偏差分布与累计分布
4.9 偏差分布 图 9 是 9 单相对承诺时刻的偏差直方图与累计分布。前 8 单的偏差集中在 \(-3\) 到 \(+1\) min,累计曲线在 0 附近陡升;第 9 单一根柱子孤零零地立在 \(-23.4\) min,把累计分布拽出一个长尾。这条长尾就是拒收风险的来源:偏差越大,客户不开门的概率越高。
按经验把拒收概率写成 \(\pi(e)=\min(0.85,\,0.03e)\),23.4 min 早到对应 70% 的拒收概率,期望损失 9.84 元。把这项加进方案 B,接单的期望成本变成 20.00 元,高过弃单的 14.0 元——结论翻转:风险口径下应当弃单。

图 10 逐单边际里程贡献
4.10 边际贡献 图 10 用剔除单法算每单的边际里程成本:把某单从最优序里摘掉,两端直接相连能省多少。第 9 单(红色)的边际贡献排在中游,说明它在几何上并不"绕";真正让它贵的不是里程,而是那 23.4 min 的时间缺口。这也解释了为什么单纯优化路线解决不了这个问题。
5 结论
-
最短路线下骑手在 36.6 min 就抵达第 9 单楼下,早到 23.4 min,这是 8 单送得太快造成的结构性缺口,不是排程失误。
-
硬左窗模型如果把等待变量排除在目标之外,账面只多 1.97 元,实际隐藏 8.20 元闲置;这种"零成本"是建模假象,必须在目标里显式定价。
-
早到等待罚金的临界值是 \(p^\ast=0.51\) 元/min(31 元/h):低于它,等着划算;高于它,弃单划算。
-
早到即拒收的规则把 8.20 元的等待成本放大成 14.0 元的订单损失,是最差的建模方式;它的唯一用处是暴露规则本身的代价。
-
计入拒收概率后,早到 23.4 min 对应 70% 的拒收风险,期望损失 9.84 元,接单转为劣势——风险口径比确定性口径更早触发弃单。
参考文献
[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.
复现方式:python batch_044.py;数据与结果见同目录 .csv / .json,图见 figs/

浙公网安备 33010602011771号