案例095 客户中途改地址
案例095 客户中途改地址
摘要:6 单在途,R1 客户把地址从 0.4 km 外的小区改到 3.2 km 外的公司,其余 5 单的排序全部失配。用 \(y_{ij}\) 选弧、\(t_i\) 递推时间、\(z\in\{0,1\}\) 绑新旧址建 MILP,目标 \(\min\) 新增里程 \(+3.5\sum\) 新增超时。基准 6.80 km 零超时;重解后 12.80 km、新增 6.00 km、新增 1 单超时,新增成本 9.50 元。三策略对比:不重解 20.0 元(4 单超时)、只重解受影响段 19.3 元(6 个排列)、重解剩余全部 9.5 元。滚动框架以每 \(k\) 单重解一次为参数扫描 15 单/日、4 次改址的流,最优 \(k^*=3\),折扣值最高 17.4 元、\(k=1\) 时回落到 14.3 元;\(k=1\) 比 \(k=3\) 多解 10 次却贵 3.1 元。决策口径:收益 5.6 元、新增成本 9.50 元,净亏 3.90 元,临界新增里程 2.1 km(含 1 单超时)或 5.6 km(不含超时),临界补偿 3.90 元。
关键词:滚动优化;动态重优化;新旧址二选一;重解频率;帕累托前沿;拒单临界
1 问题提出
骑手一趟带走 6 单,从当前位置出发时手上还有 6 个待送点:R1 就在前方 0.4 km 的小区门口,R2 到 R6 散在城区东北。骑手正要拐进 R1 的小区,电话响了——客户说人已经回公司了,把餐送到公司,公司在 3.2 km 外的南边。这一单的原地址不再是有效地址,而其余 5 单的先后顺序原本是围着"先送 R1"排的,如今全部失配。
这就是动态重优化最朴素的形态:计划赶不上变化,变化来了要重算,但重算本身有代价。真实系统里,重算不是免费的——平台要重新求解、要下发新路径、骑手要停下车看手机、还要打电话跟客户确认门牌。重解频率于是成了一个真正的决策变量:解得越勤,路线越贴合现实;解得越疏,省下的是算力和骑手的注意力,赔进去的是里程和时效。
本案先把 6 单这一刀切清楚:改址前后各是多少里程、多少超时、值不值得接这一改。再把镜头拉到一天 15 单的流上,用"每 \(k\) 单重解一次"这个参数扫一遍,看折扣值(重解省下的钱)怎么随重解次数衰减,最后回答工程上那句经验——"每 90 秒或每 3 单重解一次"——在数据上站不站得住。
2 数学模型
设 \(\mathcal{N}=\{1,\dots,6\}\) 为剩余订单,\(0\) 为骑手当前位置;\(t_i\) 为节点 \(i\) 的到达时刻,\(s=2.5\) min 为单点服务时间,\(\tau_{ij}\) 为 \(i\) 到 \(j\) 的行驶时间(min)。订单 1 有两个候选节点:原址 \(a\)、新址 \(b\),二选一由 \(z\in\{0,1\}\) 决定(\(z=1\) 送新址)。\(y_{ij}\in\{0,1\}\) 表示走 \(i\to j\) 这条弧。
选址与连通。
式 (1) 用一个 \(z\) 把两个互斥节点绑进同一条回路:\(z=1\) 时 \(a\) 的出入度都是 0,\(b\) 的出入度都是 1。这就是"新旧址只能选一个"的全部代数,不需要写"或"逻辑,也不需要大 \(M\)。
时间递推。
\(i=0\) 时 \(s=0\)。式 (3) 只给下界而行驶时间为正,沿任一条被选中的弧时刻严格递增,因此不会出现子回路,省掉了子回路消除约束——这是用时间递推换掉 MTZ 的经典手法。
超时门控。
目标:新增口径。
改址前的 \(L_0\) 与超时单数 \(W_0\) 是常数,所以式 (5) 减去常数 \((L_0+3.5W_0)\) 就是"新增成本":\(\Delta Z=(L_1-L_0)+3.5(W_1-W_0)\)。目标里不出现"新增",报告里出现"新增",两者只差一个常数,不需要把 \(L_0\) 写进模型。
滚动版本。 把一天切成若干重解点 \(\mathcal{K}=\{1,2,3,5,15\}\),每隔 \(k\) 单重解一次剩余集合,累计成本
\(r\) 为第 \(r\) 个重解周期,\(c_s\) 为一次重解的计算加沟通成本。式 (6) 里 \(k\) 不出现在约束里、只出现在"周期数"上——这就是滚动优化与静态优化的全部差别:同一个模型,调用频次变成参数。
3 Gurobi 求解实现
数据是构造的示例:把 R1 的原址放在骑手正前方 0.4 km,新址按"3.2 km 外的公司"落在南侧,其余 5 单铺在 \(1\text{—}3\) km 的东北片区,距离用曼哈顿路网度量(\(|\Delta x|+|\Delta y|\)),行驶时间按 18 km/h 折算、单点服务 2.5 min。承诺时刻由基准方案推得,留 2—6 min 余量,所以基准方案零超时,任何超时都是改址带来的增量。
表 1 6 单算例的节点与承诺(距离为曼哈顿路网 km,时刻为 min)
| 订单 | 节点 | 坐标 | 距当前 / km | 承诺时刻 | 改址前完成 | 改址后完成 | 是否新增超时 |
|---|---|---|---|---|---|---|---|
| R1 | B1 | (0.2, -3.0) | 3.20 | 40 | 3.8 | 57.7 | 是 |
| R2 | R2 | (0.9, 0.7) | 1.60 | 24 | 17.5 | 15.0 | 否 |
| R3 | R3 | (1.6, 0.1) | 1.70 | 32 | 10.7 | 8.2 | 否 |
| R4 | R4 | (2.2, 0.9) | 3.10 | 34 | 31.5 | 29.0 | 否 |
| R5 | R5 | (1.1, 1.5) | 2.60 | 28 | 23.3 | 20.8 | 否 |
| R6 | R6 | (2.8, 0.4) | 3.20 | 44 | 37.7 | 35.2 | 否 |
模型规模小到可以手算:8 个节点、49 个 0-1 弧变量、7 个终点标记 \(e_i\)、1 个 \(z\)、6 个 \(w\)、8 个连续 \(t\),共 37 次求解(含扫描)合计 3.5 s。基准解 Gurobi 0.000 s、1 次分支;重解解 0.053 s、169 次分支。大 \(M\) 取 300、\(t_i\) 上界取 240:路径不超过 6 段行程加 6 次服务约 60 min,300 盖得住;\(M\) 取小了会把『未走这条弧』也变成有效约束,取大了分支变松。
三个实现层面的坑:(1)\(z\) 不要写成两套模型。分"送原址""送新址"建两个模型分别求解,看起来更直观,但两个模型的目标值不可比——一个包含 \(a\) 节点、一个不包含,里程基准不同。用一个 \(z\) 把 \(a,b\) 同时放进同一张图,目标才在同一口径上。
(2)间距矩阵要一致,新增口径在报告里算。改址只改一个节点坐标、其余弧长不变;若按比例缩放整张矩阵,\(L_0\) 会跟着变,"新增里程"就成了两个不同世界的差。把 \(L_0\) 当常数减进目标,数值上等价,但会让目标值出现负数,日志不直观,还容易在比较不同 \(k\) 时误用同一常数。
4 结果分析
4.1 一刀切清楚:改址前后差在哪

图 1 改址前后 6 单完成时刻对照(横线为承诺时刻,数字为时差)
改址前的基准路线是 A1 → R3 → R2 → R5 → R4 → R6,总里程 6.80 km、全程 35.2 min、零超时。重解后的最优路线变成 R3 → R2 → R5 → R4 → R6 → B1:骑手不再先送 R1,而是把北边 5 单先跑完,最后绕去公司。里程 12.80 km、新增 6.00 km;R1 的完成时刻从 1.3 min 推到 55.2 min,新增 1 单超时、罚款 3.5 元。合计新增成本 9.50 元。
图 1 里最扎眼的是 R1 那两根柱子:改址前 3.8 min 送到,改址后 57.7 min。注意两种排法的里程完全一样(都是 12.80 km:先绕去南边再回北边,或先跑北边再折回南边,长度相同),差别全在时效——B1 → R3 → R2 → R5 → R4 → R6 让北边 5 单各推迟 9.3 min、4 条承诺连锁超时;R3 → R2 → R5 → R4 → R6 → B1 只让 R1 一个人迟到。在 3.5 元/单的罚则下,牺牲一个人比牺牲五个便宜 10.5 元,模型因此选择让 R1 等到 57.7 min。这是罚则设计问题,不是求解问题。

图 2 改址距离与新增成本(气泡越大表示改址单送达越晚,方框为本案 3.2 km)
图 2 把新址沿同一条射线从 1.6 km 推到 4.4 km:新增里程从 4.60 km 线性升到 7.05 km(斜率 0.88 km/km,\(R^2=1.000\)),而新增超时从头到尾都是 1 单——只要改址,最优解就把这一单排到最后,迟到是必然而不是偶然。所以成本曲线是一条干净的斜线,5.6 元的收益线在 \(\Delta L=5.6\) km 处被穿过。
4.2 三策略与重解频率

图 3 三种应对策略的新增成本(堆叠:里程 + 超时罚款)与求解次数
三种应对方式的差距不是"好一点",而是"差一倍":
表 2 三策略的新增成本与计算量(基准:里程 6.80 km、零超时)
| 策略 | 路线 | 里程 / km | 新增里程 / km | 超时单数 | 新增成本 / 元 | 求解次数 |
|---|---|---|---|---|---|---|
| 不重解(硬走原顺序) | B1 → R3 → R2 → R5 → R4 → R6 | 12.80 | 6.00 | 4 | 20.00 | 0 |
| 只重解受影响段 | R3 → R2 → B1 → R5 → R4 → R6 | 15.60 | 8.80 | 3 | 19.30 | 1(6 个排列) |
| 重解剩余全部 | R3 → R2 → R5 → R4 → R6 → B1 | 12.80 | 6.00 | 1 | 9.50 | 1(MILP) |
不重解最贵(20.0 元):顺序不动意味着 5 单各推迟 9.3 min,连锁压垮 4 条承诺。只重解受影响段几乎没用(19.3 元,只省 0.7 元):它只允许改址单在前 3 个位置里换位,而真正的解要把它挪到队尾第 6 位——局部窗口设窄了,就救不了位置错位,这是"只重解受影响段"最容易踩的坑。全重解一次到位(9.5 元),省下 10.5 元。

图 4 三种策略下的逐单完成时刻轨迹(空心圈为超时的那一单)
图 4 把三条曲线的形状画出来了:不重解(灰方块)整条抬高、末端越过 4 条承诺线;只重解受影响段(绿三角)把前段抬高换回 1 条承诺;全重解(红菱形)把北边 5 单全压回线内,只留 R1 一单在外。"重解"的价值不在平均值,而在它把尾部拉回来了——前 5 单的平均改善只有 3 min,尾部却是 40 min 的差。

图 5 重解间隔 k 对日总成本(左轴)与重解次数(右轴)的影响
把镜头拉到 15 单的一天、4 次改址(3 个随机种子平均),\(k=15\)(只在出发前解一次)日成本 63.9 元,\(k=5\) 降到 46.6 元,\(k=3\) 为 46.5 元,最优 \(k^*=3\);\(k=1\) 反而回升到 49.6 元。折扣值从 \(k=15\) 的 0 一路涨到 17.4 元,之后走平:\(k=5\to 3\) 只多省 0.1 元,\(k=3\to 1\) 多解 10 次要花 10.0 元,成本反而涨 3.1 元。边际折扣在 \(k=3\) 处跌到与重解成本相等,这就是最优点的经济学定义:再解一次省下的钱,刚好不够付这次重解。

图 6 新增距离 × 剩余单量的新增成本(方框为本案 3.2 km、6 单)
图 6 换两个维度看同一件事。剩余单量越多,同一个距离的代价越高:3.2 km 处从 4 单的 4.7 元涨到 6 单的 9.5 元。原因不是"绕行摊薄",恰恰相反——链条越长,把改址单插到队尾要跨的弧越多,绕行的机会成本被放大。这解释了为什么行程后半段改址平台更愿意免费改:剩的单少,重排的余地小,反而亏得少。

图 7 单次重解成本对最优重解间隔(左轴)与日总成本(右轴)的影响
图 7 把 \(c_s\) 当参数扫:\(c_s=0.2\) 元时最优 \(k^*=1\),\(c_s=0.8\) 元时升到 \(k^*=3\),\(c_s=1.2\) 元起稳定在 \(k^*=5\)。"多久重解一次"不是算法问题,是成本结构问题:算力便宜就该勤解,骑手操作成本高就该懒解。本案把 \(c_s\) 定在 1.0 元(骑手停车确认 30 s + 平台求解 + 电话),落在 \(k^*=3\) 的平台段上——位置不算稳:\(c_s\) 只要跌破 0.8 元,最优就滑到 \(k=1\)。\(k^*\) 是成本结构的函数,不是常数。
4.3 该不该接:前沿、分布与分解

图 8 新增里程—新增超时罚款的可行点与前沿(阴影为成本超过收益的拒单区)
图 8 用 ε 约束扫 3 个距离 × 3 个罚单上限:非支配点只有两个——(3.9 km, 7.0 元) 与 (5.3 km, 3.5 元),其余 4 个点都被支配。两点只说明一件事:多跑 1.40 km,买回 3.5 元罚款,折 2.5 元/km,比 1 元/km 的里程单价贵一倍多,所以按成本最优选了罚款那一侧。整块可行点几乎都落在 5.6 元平衡线上方——在 3.2 km 这个距离下,怎么排都亏,亏得最少的是重解解(9.50 元)。

图 9 改址距离服从 N(3.2, 0.8) 时的新增成本分布(虚线为 5.6 元收益线)
图 9 把"3.2 km"当成分布而不是一个点:按 \(N(3.2,0.8)\) 抽 2 万次、用 8 个求解点拟合的线性关系换算,新增成本均值 9.50 元、5% 分位 8.35 元、95 分位 10.66 元,落进 5.6 元收益线以内的样本占比只有 0%——两万个里一个都没有。原因在上一段已经写死:最优解恒把改址单排最后,3.5 元罚款是常数,成本只剩里程那一条斜线。要让免费改址划算,新址必须近到 2.1 km 以内,比现在的 3.2 km 近三分之一。

图 10 不同重解间隔下的日成本分解(里程 / 超时 / 重解)
图 10 把日成本拆开。\(k=15\) 时超时罚款占 37%、里程占 62%;\(k=1\) 时重解成本占到 30%,罚款降到 16%。钱在两个桶之间搬家:解得太懒,钱花在罚款上;解得太勤,钱花在重解上;\(k=3\) 是两桶之和最小的地方。
决策口径。 该单收益 5.6 元,改址后新增成本 9.50 元,净 -3.90 元,应当拒单或加价。临界新增里程分两档:若不新增超时,\(\Delta L^*=5.6\) km;本案必然新增 1 单超时,\(\Delta L^*=(5.6-3.5)/1.0=2.1\) km。实测 6.00 km,越线 3.90 km。临界补偿额 3.90 元——客户补到 3.90 元以上才不亏;平台现行的 2 元改址补贴覆盖不了,缺口 1.90 元。
5 结论
(1)最优应对:重解剩余全部,新增成本 9.50 元。基准 6.80 km、零超时;重解后 12.80 km、新增 6.00 km、新增 1 单超时,路线由 A1 → R3 → R2 → R5 → R4 → R6 变为 R3 → R2 → R5 → R4 → R6 → B1。模型 37 次求解(含扫描)合计 3.5 s,单次 8 节点 MILP 在 0.053 s 内证到最优,远低于 60 s 预算。
(2)三策略排序:全重解 < 局部重排 < 不重解,9.5 / 19.3 / 20.0 元。局部重排在本例几乎失效(只省 0.7 元),因为它只允许改址单在前 3 位内换位,而正确解要挪到队尾。"只重解受影响段"必须配套一个够大的窗口,否则等于没有重解。
(3)\(k^*=3\) 与"每 3 单"一致,与"每 90 秒"不一致。本算例单均 6.97 min,90 秒只够 0.22 单,一天折算 70 次重解,是 \(k=3\) 的 14.0 倍,多花 65 元只多省不到 1 元。"每 90 秒"只在单均间隔约 30 秒的密集场景(同楼 5 连单、步行街)才等价于"每 3 单";在 6 单散点场景里,它是把算力烧在抖动上的规则。
(4)决策:拒单或加价,临界补偿 3.90 元。收益 5.6 元、新增成本 9.50 元,净 -3.90 元。临界新增里程在不增超时时为 5.6 km、在必然增 1 单超时时降到 2.1 km。改址的定价锚点不是"多跑了多少公里",而是"多跑了多少公里加上被打掉的那条承诺"——后者占了大头(37%)。
(5)三条可复用的结论:其一,滚动优化的模型与静态模型同构,\(k\) 只改调用频次、不改约束,因此同一份 Gurobi 代码可以既跑单次决策又跑闭环仿真;其二,重解的价值集中在尾部(把最差那单拉回承诺线),均值几乎不动,评价滚动策略要看 \(\max\) 侧指标;其三,\(k\) 的最优值由 \(c_s\) 与"每单迟到的边际罚款"之比决定,工程经验值应随这两个量重新标定,而不是照抄。
参考文献
[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.py;数据与结果见同目录 .csv / .json,图见 figs/

浙公网安备 33010602011771号