应急物资配送的公平性专攻:Gini 画像、最差优先的陷阱与能力层杠杆(优秀范文三)
摘要
范文一完成了「选址—路径—动态再调度」一体化,范文二把准时率从 43.3% 提到 76.7%。但应急配送还有一个常被平均数掩盖的维度:公平——最慢的那批人到底多慢、为什么慢、该用什么手段救?本文以范文一同款基线解(准时率 43.3%)为解剖对象,新增八组实验,得到四个结论:第一,服务严重不均——30 个需求点的到达时间从最快 33 分钟到最慢 359.6 分钟相差近 11 倍,Gini 系数高达 0.486;第二,病根在「能力半径」而非排序技巧——最差 5 点全部是车辆路线末端(顺位 7/1111/11),路网里程 12.420.8 km 而欧氏距离仅 3.1~4.0 km,迂回系数高达 4 倍;第三,「最差优先」重排是陷阱——它确实把 Gini 压低 5.5%,但最大到达反而恶化 1.5%、均值恶化 4.4%,等于换了一批苦主;给无人机加倍也只是让 5 个近仓点更早,两极分化拉大(Gini 反升 4.5%);第四,真正的公平杠杆在能力层:把无人机续航半径从 8 km 扩到 12 km,可达集从 5 点扩到 9 点,均值降 14.9%、成本降 17.5%、最大到达降 9.1%,是唯一同时改善公平、时效与成本的帕累托改进;中断冲击测试进一步显示,中断率升至 20% 时最差点到达跳崖至 512.5 分钟(+42%)、Gini 冲上 0.5415。应急方案的公平性必须按断崖情景而非平均情景来设计。
一、问题重述与数据
赛题要求在 5G 使能背景下对应急物资做配送优化:30 个需求点散布在 60 km 见方的区域内,8 个候选前置仓选 3 个开设,地面车辆与无人机协同送达。范文一的基线解给出了完整方案:p-中位选址、NN+2-opt 路径、无人机短途直达,总成本 2,004 元、平均到达 128.7 分钟。
本文不改前两篇的任何结论,只把镜头对准被「平均值」遮住的那一半分布:谁被落下了? 全部实验复用范文一的真源场景(固定种子)与求解器,只新增公平性的度量与干预手段。
二、结果 I:服务公平画像
图 1 把 30 个需求点按到达时间升序排列:中位 106 分钟,P90 高达 277 分钟,最慢 359.6 分钟与最快相差近 11 倍,Gini 系数 0.486——在应急语境下,这意味着最弱势的一批需求点要等将近 6 个小时。P90 与中位之间 171 分钟的鸿沟进一步说明:落后不是个别点的偶然现象,而是系统性的尾部堆积。标红的 5 个最差点(#11、#36、#32、#22、#9,到达 263~360 分钟)有一个惊人的共同点:它们全部由车辆配送,没有一个吃到无人机红利。
三、结果 II:最差服务归因
图 2 对最差 5 点逐一体检,找到三个共同特征。其一,全部是车辆末端:顺位依次为 11/11、10/11、9/11、8/11、7/11,即整条路线的最后五个位置。其二,路网里程与欧氏距离严重背离——#36 路网 20.8 km 而直线仅 4.0 km,#11 路 12.4 km 直线 3.1 km,路网中断叠加道路迂回把实际车程放大到直线的约 4 倍;这也解释了它们为何够不上无人机的 8 km 续航半径(按欧氏距离判定)。其三,时间窗截止并非主因:#9 的窗止在 320 分钟、并不紧迫,却依然迟到。顺位末端也并非调度员失职——车辆沿最近邻加二优化的路线行进,远点天然沉底;而路网中断让「绕不过去的弯」越来越多,两个因素相乘才把尾部拖到六小时量级。结论:落后不是「排错了队」,而是「车开不到」——病根在能力半径。
四、结果 III:最差优先的陷阱
决策树分裂准则
ID3算法使用信息增益选择最优分裂属性:
其中熵 , 为第 类样本占比。
C4.5改用信息增益比,CART使用Gini指数:
Gini指数越小,节点纯度越高。
最直觉的公平手术是把每条车辆路线倒过来:先送最远的、后送最近的。图 3 显示这场手术的真实疗效:Gini 从 0.486 降至 0.460(−5.5%),不平等确实收敛了;但最大到达从 359.6 升到 364.9(+1.5%),均值从 128.7 升到 134.3(+4.4%)——原本先被服务的近仓点被推到了队伍尾部,成了新的苦主。同时总行驶里程反而缩短 6.6%,说明原 NN+2-opt 解远非路径最优。教训:在能力不变的前提下,排序层的公平再分配本质上是零和甚至负和游戏——你无法用一条固定能力的车队同时讨好所有人。
五、结果 IV:紧迫度加权视角
需求点自带 1~10 的紧迫权重。图 4 左侧显示高急组(权重≥8,n=10)平均到达 127.4 分钟,快于低急组(≤3,n=13)的 145.8 分钟,权重加权均值 127.8 也低于无权重均值——现行解隐含了轻度的时间窗驱动偏好。但右侧的迟到分解揭示了另一面:高急组迟到合计 75 分钟、低急组合计 94 分钟,高急组仍占全部迟到分钟的 44%。若以「最急的人不能等」为准则,现状不合格;而 E3 已证明排序层腾挪空间有限,这再次把矛头指向能力层。一个可执行的折中是给高急点设置「迟到熔断」:一旦滚动预测的到达时间将超出窗止,立即触发无人机增援或二次出车,把对紧迫需求的保护从隐式偏好升级为显式约束。
六、结果 V:加无人机为何适得其反
既然无人机快,多派几架行不行?图 5 给出反直觉答案:机数从 1 加到 3,无人机可达点恒为 5 个(半径 8 km 与载重 5 kg 双门槛卡死),最差 5 点纹丝不动(最大到达恒为 359.6),唯一的效应是那 5 个近仓点的串行等待被并行摊薄、到达大幅提前——两极分化反而拉大,Gini 从 0.486 升到 0.508(+4.5%)再到 0.516。且该模型按架次计费,加机分文不花却买不来公平。「加资源」若不加在约束的瓶颈侧(续航半径),只是在富裕侧锦上添花。
七、结果 VI:扩续航半径才是公平杠杆
图 6 扫描无人机续航半径 6→16 km:可达集从 2 点扩到 12 点;半径 12 km 时均值 109.5 分钟(−14.9%)、总成本 1,653 元(−17.5%)、最大到达 326.9 分钟(−9.1%)、Gini 回落到 0.4625;16 km 时最大到达 237.1 分钟(−34.1%)、Gini 0.411,全面优于基线。机理正是 E2 的诊断镜像:无人机按欧氏直线飞行、不受路网迂回与排队末端拖累,半径每扩大一档,就有一批「路网囚徒」被解放出车辆长队。这是全文唯一同时改善公平、时效与成本的杠杆——公平投资的最优标的是能力,不是顺序。
八、结果 VII:公平—效率平面
把四种配置放到「平均到达 × Gini」平面(图 7):基线(128.7, 0.486)、纯重排(134.3, 0.460)、重排+双机(131.6, 0.482)、重排+双机+12 km(109.5, 0.503)。在本实例上公平与效率呈现真实的张力:均值最低的配置 Gini 最高,因为大量远点被无人机快速送达后,剩余车辆点的相对落差反而凸显。这提示决策者必须先声明目标函数——要「均等」(选左下 Gini 低者)还是要「都快」(选右侧均值低者)——两者在本网络上不可兼得,含糊其辞只会得到两头不讨好的折中。
九、结果 VIII:中断冲击下的公平韧性
最后做压力测试(图 8):中断率 5% 时最差点 335.5 分钟、10% 时 359.6、15% 时 320.7——小幅波动、非单调(扰动改变的是最短路网络形态而非简单加时);20% 时跳崖至 512.5 分钟(较基线 +42%),Gini 同步冲上 0.5415。准时率则自 10% 中断起钉死在 43%。非单调与断崖并存说明:路网退化的伤害不是线性的累积,而是关键边断裂后的系统性改写。应急方案的冗余预留必须锚定断崖情景,而不是平均情景。5G 网络在此的价值正是提前预警:实时路况与中断信息越早进入再调度,断崖到来之前的缓冲窗口就越大。
十、结论
八组实验把「公平」从口号变成可操作的工程判断:
- 度量先行:Gini + 最大到达双指标才能同时暴露「普遍不均」与「极端落后」,均值会同时掩盖两者;
- 归因决定药方:当落后由能力半径(路网迂回、续航门槛)导致时,排序层的一切腾挪都是零和的;
- 资源必须打在瓶颈侧:加无人机架次而不扩半径,等于在富裕侧堆料,公平反而恶化;
- 扩续航半径是稀缺的帕累托改进:12 km 半径让公平、时效、成本三者同降,16 km 时最大到达缩短三分之一;
- 韧性设计锚定断崖:20% 中断下最差点恶化 42%,任何平均情景下的优化都无法覆盖这种尾部风险。
十一、模型验证(四路一致)
本文正文、配图、附录代码、真源 tools/gen_dgcup2022b_3.py 四路数字完全一致。场景与基线复用 gen_dgcup2022b.py 固定种子输出,本脚本所有实验为确定性计算、不含新随机数,双跑逐位一致;附录代码块可在 tools/ 目录独立运行复现全部关键数字(Gini 画像、五点归因、重排对照、加权分解、三档机数、五档半径、四配置前沿、四档中断)。
参考文献
[1] 电工杯全国大学生数学建模竞赛组委会. 2022 年 B 题:5G 网络环境下应急物资配送问题.
[2] Bertsimas, D., Farias, V., Trichakis, N. On the Fairness-Efficiency Tradeoff. Management Science.
附录:核心 Python 实现
import sys, os
_HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.abspath(os.path.join(_HERE, "..", "..", "..", "tools")))
import gen_dgcup2022b_3 as SRC
r = SRC.gen_dgcup2022b_3()
# E1 公平画像
m = r["base_m"]
print("E1 mean %.1f p90 %.1f max %.1f gini %.4f" % (m["mean"], m["p90"], m["max"], m["gini"]))
for w in r["e1"]["worst"]:
print("E1 #%d 到达 %.1f (%s)" % (w["id"], w["arr"], w["mode"]))
# E2 归因 / E3 重排
for a in r["e2"]:
print("E2 #%d 路 %.1fkm 欧 %.1fkm 顺位 %d/%d" % (a["id"], a["road_km"], a["eu_km"], a["seq"], a["n_pts"]))
m3 = r["e3"]["m"]
print("E3 gini %.4f max %.1f Lv %.0f->%.0f" % (m3["gini"], m3["max"], r["e3"]["tot_lv_old"], r["e3"]["tot_lv_new"]))
# E4 加权 / E5 机数 / E6 半径
print("E4 wavg %.1f 高急均 %.1f 低急均 %.1f" % (r["e4"]["wavg"], r["e4"]["hi_mean"], r["e4"]["lo_mean"]))
for nd, v in sorted(r["e5"].items()):
print("E5 机=%d gini %.4f 成本 %.0f" % (nd, v["m"]["gini"], v["cost"]))
for rr, v in sorted(r["e6"].items()):
print("E6 半径%.0f 可达%d avg %.1f 成本 %.0f gini %.4f" % (rr, v["n_drone_eligible"], v["avg"], v["cost"], v["m"]["gini"]))
# E7 前沿 / E8 中断
for cf in r["e7"]:
print("E7 %-12s mean %.1f gini %.4f max %.1f" % (cf["cfg"], cf["mean"], cf["gini"], cf["max"]))
for dd, v in sorted(r["e8"].items()):
print("E8 中断%.2f max %.1f gini %.4f" % (dd, v["max"], v["gini"]))