简介:《鲜奶配送站点的最优化设置问题》PDF面向数学建模竞赛选手、运筹优化与供应链管理学习者,围绕92个鲜奶网点的选址与配送难题展开,适合用作校赛练手、课程案例或赛前复盘材料。文件为1个PDF,压缩包约98KB,篇幅精炼,集中呈现赛题背景、三问要求及附件数据说明:在给定网点坐标、订奶量与配送道路的条件下,分别设计最经济的站点设置方案、满足10分钟配送时限的多站点方案,并兼顾各站配送量均衡。内容涉及设施选址、车辆路径与库存控制等建模思路,可帮助读者理解如何把配送速度、建设成本、在途时间和资源利用率纳入目标与约束,进而完成模型假设、求解与方案比较。已有3074人学习,适合希望用完整赛题串联选址—路径—均衡分析的中高级建模练习者。
1. 鲜奶配送站点选址:为什么 92 个网点不能靠“画圈”解决
鲜奶配送站点选址这件事,直觉上像是地图上画几个圈:圈住网点、圈住订奶量,选个中心点,问题就布置完了。真把 92 个网点的坐标、订奶量和道路数据摆到桌上,你会发现画圈很快就站不住脚。三个约束层层叠加:第一层是最经济的配送站设置方案,配送速度、配送成本、建设成本三者必须同时进入目标函数;第二层是 10 分钟送达约束,20km/h 的时速意味着单程距离被限制在 3.3 公里以内;第三层与配送量均衡有关:一个站配得太少是资源浪费,配得太多又会延误。这三层约束使选址问题从几何画圈变成组合优化。这篇要拆的就是这类“最后一公里”选址模型为什么这么建、约束为什么这么列,以及在 92 个候选点、多个站的规模下,求解器到底在搜索什么空间。
2. 设施选址模型:从 92 个候选点到 0-1 决策变量的映射
2.1 候选点即决策变量:为什么站只能建在网点上
题目有一句关键的限定:在 92 个网点位置中选择一个或多个点建设配送站。这句话把连续选址问题压缩成了离散选址问题,候选集就是那 92 个坐标点本身。常见的实现思路是用一组 0-1 变量表示“第 j 个网点是否建站”:
# y[j] = 1 表示在第 j 个网点建站,0 表示不建 # x[i][j] = 1 表示第 i 个网点的鲜奶由第 j 个站配送 # demand[i] 为网点 i 的订奶量,cost[j] 为在 j 建站的固定成本 y = {j: model.addVar(vtype="B", name=f"y_{j}") for j in nodes} x = {(i, j): model.addVar(vtype="B", name=f"x_{i}_{j}") for i in nodes for j in nodes} # 每个网点的需求必须被某个已建站覆盖,且只能由一个站服务 for i in nodes: model.addConstr(sum(x[i, j] for j in nodes) == 1) for j in nodes: model.addConstr(x[i, j] <= y[j]) # 只有建了站才能分配上面这段代码里,x[i, j] <= y[j]是最容易被忽略的一行。它表达的逻辑是:若 j 没建站(y[j]=0),则任何网点都不能声称由 j 服务(x[i, j]必须为 0)。没有这一行,求解器会给出“所有需求都分配给成本最低的虚拟站”这种看起来极优、实际上不可行的解。另一行sum(x[i, j]) == 1则锁定“单源配送”——每个网点只由一个站供货,符合鲜奶按片区配送的实际。
变量规模值得算一笔账:92 个节点时,y有 92 个,x有 92×92=8464 个,再加上约束,这是一个中等规模的混合整数规划。用常见的开源求解器就能求解,但前提是把目标函数写对。
2.2 目标函数的三块成本:为什么“最经济”不等于“站最少”
题目要求综合考虑配送速度、配送成本和建设成本。最经济的写法是把三类成本合成一个标量目标:
# 目标:最小化 建站固定成本 + 运输成本 + 时间惩罚 obj = ( sum(cost[j] * y[j] for j in nodes) # 建设成本 + sum(demand[i] * dist[i][j] * unit_transport * x[i, j] for i in nodes for j in nodes) # 运输成本 ∝ 订奶量×距离 + sum(demand[i] * dist[i][j] / speed * time_weight * x[i, j] for i in nodes for j in nodes) # 在途时间惩罚项 ) model.setObjective(obj, GRB.MINIMIZE)这里的核心取舍是:建设成本是“开一个站交一份钱”的阶跃成本,运输成本则正比于“订奶量 × 距离”。站建得越多,运输距离越短,但固定成本越高;站建得越少,固定成本省了,运输成本和在途时间又上去了。目标函数本质上是在这两股力量之间寻找平衡点。所以第一问的答案通常是一个站,或者极少数几个站这样的小数目,这些站会集中在订奶量密集的区域,而配送速度并不需要单独建模,它会与运输成本合并:速度越高,单位距离的时间惩罚越小,等价于缩短了“心理距离”。
参数time_weight和unit_transport是这道题的调参入口。前者把 10 分钟折算成成本量纲,后者把公里折算成元。两者没有唯一正确的取值,常见做法是先取 1:1,再做灵敏度分析——看建站数量随参数比值怎么跳变,跳变点往往就是方案的分水岭。
2.3 为什么站位被限制在网点上:建模简化与现实的差距
把候选点限定为 92 个网点,是一种刻意的建模简化。真实选址中,站点可以建在任意空地上,那将是 p-中值问题(p-median)的连续版本,需要先做候选点离散化——比如按道路交叉口、地块中心生成候选集。竞赛题为了避免这种扩散,直接把候选集锁定成网点集合。这种做法的好处是约束好写、求解规模可控;代价是解空间被强制收窄,可能错过“在两个网点之间建一个站更优”的方案。理解这一点,才能解释为什么题目强调“在 92 个网点位置中选择”——它决定了模型的上限,也决定了求解器只需在 2 的 92 次方量级里搜索。
3. 十分钟时效约束:距离阈值如何把选址变成集合覆盖
3.1 从时速 20km/h 反推 3.3 公里半径
第二问给了一个硬约束:配送时间不超过 10 分钟,时速 20km/h。换算非常直接:20km/h 约等于 0.333km/min,乘以 10 分钟,单程可达距离约 3.33 公里。这条约束把连续的距离关系变成了 0-1 的可达关系——对每一对(网点 i,站点 j),先判断距离是否落在阈值内,再决定这条配送弧是否存在:
R = 20 / 60 * 10 # 时速 20km/h,10 分钟,得 3.33km reachable = {} for i in nodes: for j in nodes: reachable[i, j] = 1 if dist[i][j] <= R else 0 # 覆盖约束:每个网点必须至少被一个可达的已建站覆盖 for i in nodes: model.addConstr( sum(reachable[i, j] * x[i, j] for j in nodes) == 1 )这段代码是整道题的建模分水岭。第一问里距离是成本的一部分,可以“花钱买距离”;第二问里距离变成可行性门槛,超过 3.33 公里的分配直接不允许,目标函数退化为“在满足覆盖的前提下最少建站、最低成本”。这就是设施选址与集合覆盖两类模型的差别:前者在成本曲线里找平衡,后者先用阈值把不可行解剔除,再在最少的站数里优化成本。
3.2 覆盖矩阵的稀密程度决定问题难度
把 92 个网点两两之间的距离算出来,再按 3.33 公里的下界统计每个网点的可覆盖站数,会发现分布极不均匀:城市中心区的网点可能有几十个候选站落在半径内,边缘网点可能只有一两个。这种稀疏性正是求解器能解出结果的原因——覆盖矩阵中大量 0 让分支定界剪枝非常有效。
注意:如果直接把欧氏距离当作配送距离,会低估实际路程。道路数据给的是网点之间的连通关系,常见做法是在道路图上跑一遍最短路(Dijkstra 或 Floyd),得到路网距离矩阵,再与 3.33 公里比较。用直线距离代替路网距离,会让边缘网点被误判为“可达”,最终方案上线时才发现某些片区根本送不到。
这也是这道题容易被忽略的工程细节:公告没写清“距离”指哪种距离,但附件既然提供了道路数据,就暗示应当用路网最短路。计算量上,92 个节点跑 Floyd 是 92³ 量级,几十万次操作,完全可以接受。
3.3 多站方案的组合爆炸与剪枝
第二问的可行解空间比第一问大得多:满足 10 分钟覆盖的前提下,可能的站点组合数量极多。常见的剪枝思路有两条。其一是先算出“必须建站”的网点——如果某个网点的可达集合里只有它自己,那它必须建站,这是硬性下界。其二是先求最小覆盖集(最少站数),再在这个最小数量上优化运输成本,把两目标拆成字典序求解:
# 两阶段:先最小化站数,再把站数固定为最优值后最小化运输成本 model.setObjective(sum(y[j] for j in nodes), GRB.MINIMIZE) model.optimize() min_stations = round(model.ObjVal) model.addConstr(sum(y[j] for j in nodes) == min_stations) model.setObjective( sum(demand[i] * dist[i][j] * x[i, j] for i in nodes for j in nodes), GRB.MINIMIZE ) model.optimize()字典序求解的好处是把“建设成本”和“运输成本”分先后处理,避免量纲不一致导致的权重玄学。如果两者必须同时优化,就需要像 2.2 那样人为设定权重比,而权重一改,结论就可能变;字典序至少能给出一个“站数最少前提下的最省方案”,对管理者更容易解释。
4. 配送量均衡:为什么“最省”的方案不一定能落地
4.1 不均衡的两种极端:闲置站与过载站
第三问把前两问的解重新暴露出来看:一个站配送量过低是资源浪费,过高会延误。前两问只优化了成本,完全没有约束每个站承担的订奶量。求解器为了省钱,很可能把大量网点压给一个中心站,让另外几个站几乎空转——从数学上看成本最低,从运营上看却是个黑匣子:中心站早高峰必然爆仓,闲置站又占着固定成本。
常见的均衡化建模方式有两类。一类是加“容量上限”,即给每个站的配送量设一个硬上界,类似车辆路径问题里的车辆载重:
# 每个站的配送量不超过容量上限 Q for j in nodes: model.addConstr( sum(demand[i] * x[i, j] for i in nodes) <= Q * y[j] )另一类是加“偏差惩罚”,允许不均衡但让它变贵,用偏差绝对值变量d[j]表示第 j 站配送量与均衡水平的距离,把sum(d[j])乘上惩罚系数进目标函数。前者是硬约束,写错一个系数整道题会不可行;后者是软约束,能给出“均衡到什么程度、成本涨多少”的连续曲线,对决策更有信息量。
4.2 均衡水平的定义:平均数、中位数还是容量比
均衡的“标准”本身就是一个建模决定。常见三种选法:全城总订奶量除以站点数(平均数)、按各站覆盖网点的需求量加权、或者按站点的计划服务能力做比例分配。用平均数最省事,但会出现“所有站都朝平均数靠”的约束力过强问题;按容量比更贴近现实,因为站点本身有大小之分,大站本来就该多配。
常见问题:直接把“每站配送量相等”写成等式约束,往往导致模型不可行。因为网点不能拆分配送(单源约束),每站承担的量是若干网点订奶量之和,这些和不可能正好相等。正确做法是用不等式给出一个浮动区间,比如每站配送量落在平均值的 80%~120% 之间,让模型在这个带内求解。
4.3 三问结果为什么必须一起看
三问不是三个孤立题目,而是一条递进的约束链。第一问给出成本最优的“理想解”,第二问叠加时效约束后站数必然增加,第三问再用均衡修正前两问的分布——每一步都在牺牲一部分经济性换取可执行性。把三问的解并列出来看建站数、总成本和各站负载,才能回答“多出来的成本买到了什么”。从建模角度看,第一问是设施选址模型,第二问是带覆盖约束的集合覆盖,第三问是带容量或偏差约束的扩展选址;模型结构一脉相承,只是约束逐层加码。理解了这条链条,就不会把三问当作三个互不相干的题目分别套模型。
5. 避坑与排查:选址建模里最容易翻车的五处
5.1 现象:求解结果里每个网点都被分配了,但没有一个站被建
原因:漏写x[i, j] <= y[j]这组耦合约束。求解器发现“不建站也能完成分配”,因为变量之间没有强制联动。 解决:把耦合约束逐对写全,求解后抽查任意一条分配关系,确认其对应的y[j]确实为 1。这是最典型的翻车点,建议建模后先在小规模数据上人工验算一遍。
5.2 现象:第二问模型不可行,或结果里站点数远超预期
原因:距离阈值取了直线距离,或者漏算了回程时间。10 分钟是单程还是往返,直接决定半径是 3.33 公里还是 1.67 公里;用直线距离又会把实际路网更远的网点误判为可达。 解决:用道路数据跑最短路得到路网距离矩阵;明确时间口径,若含装卸与返回则阈值减半;把不可达的(网点,站点)对显式剔除,让不可行尽早暴露。
5.3 现象:第三问加了均衡约束后模型直接不可行
原因:把均衡写成严格等式,或容量上限Q设得低于单个网点的最大订奶量。 解决:先用“总订奶量 ÷ 站数”估一个均衡水平,再给浮动区间;容量上限至少取最大网点订奶量的若干倍;若仍不可行,就退回软约束形式,用偏差惩罚替代硬上限。
5.4 现象:同一份数据两次运行结果不同,或换求解器结论大变
原因:未设最优性间隙,求解器在默认间隙内提前停止;或者目标函数权重比处于临界区间,微小扰动导致解的跳变。 解决:显式设置间隙参数并观察目标值下界;对关键权重做灵敏度扫描,记录建站数随权重变化的跳变点,报告时给出区间而不是单点结论。
5.5 现象:方案在纸面上成本最低,实际执行时某站早上根本送不完
原因:只优化了成本,没有校验单站的实际作业能力,比如一个站承担了几十个网点、一辆车要跑多趟。 解决:在模型外加一层后验校验——按每站负责的网点数、总订奶量、最远距离三项指标画表,超过阈值的方案即便数学最优也要回退。选址模型给的是候选,运营可行性得靠人补最后一刀。
6. 从目标函数看设计取舍:权重、距离与可解释性
这类选址题真正的难点不在写约束,而在几个建模决策上:成本权重怎么定、距离用什么度量、均衡是硬约束还是软惩罚。每一个决策都会改变最终方案,而它们都没有标准答案。
第一个取舍是权重比。运输成本、建设成本、时间惩罚三者合成一个目标时,权重比决定了建站数量。我一般会这么做:先把三者都换算成“元”,建站成本按一次性投入折算到日,运输成本按每公里每公斤计价,时间惩罚按延误损失定价。这样权重就不再是随手设的数,而有业务含义。第二个取舍是距离度量。路网最短路最真实,但计算和图数据绑定;直线距离最快,但在城市路网里误差可能超过 40%。常见做法是用路网距离做最终方案,用直线距离做快速估算和初筛,两者差距大的网点单独复核。
第三个取舍是可解释性。混合整数规划解出来的方案,管理者往往要问“为什么是这个网点而不是旁边那个”。这时可以对比两个方案的约束活跃情况:哪个约束把解卡住了——是 10 分钟覆盖,还是某站的容量上限——把活跃约束列出来,方案的说服力就来自这里。下面这张表是我通常会输出的方案对比结构:
| 方案 | 建站数 | 总成本 | 最远配送距离 | 单站最大负载 | 活跃约束 |
|---|---|---|---|---|---|
| 仅成本最优 | 少 | 最低 | 偏大 | 偏高 | 建设成本 |
| 加时效约束 | 中 | 中 | ≤3.33km | 偏高 | 覆盖约束 |
| 加均衡约束 | 中多 | 偏高 | ≤3.33km | 均衡 | 容量/偏差 |
最后一点值得说的是验证方法。选址模型的结果不该只报一个数字,而应当做三件事:对关键权重做扫描看建站数的跳变;对单站负载做后验校验;对不可行或临界情形给出替代方案。这套习惯来自一次教训——早年间我用直线距离快速算了一版方案,纸面上完美,后来换成路网距离复核,发现两个边缘网点根本覆盖不到,整版方案推倒重来。从那以后我每次做选址类建模,都强制先把路网距离矩阵算出来并对一遍覆盖关系,再让求解器进场。希望这套思路帮你少走一遍弯路,也建议去翻一翻那份赛题附件的原始数据结构,自己把距离矩阵跑一遍,很多直觉会在那一步被纠正。
本文还有配套的精品资源,点击获取