简介:这份PDF是2023年安徽建筑大学校内数学建模竞赛的鲜奶配送站点最优化设置赛题,面向数学建模参赛者、供应链管理及运筹优化方向的学习者。内容完整呈现赛题背景与附件中92个网点的位置坐标、订奶量及配送道路数据,并将问题系统拆解为最经济选址、10分钟配送时限约束下的站点配置、各站点配送量均衡三个子问题,分别对应设施选址、车辆路径与库存控制等典型数学模型思路,便于读者理解从问题分析到模型构建的完整链条。资源为单个PDF文件,压缩包仅约98KB,内容精炼,适合打印阅读或反复推敲。目前已有3073人学习下载,对于备战数学建模竞赛或研究城市生鲜配送选址问题的读者具有直接参考价值。通过研读该赛题,可以掌握多目标优化、时间窗约束处理和配送均衡性分析等实用建模方法,有助于提升解决实际物流难题的能力。
1. 鲜奶配送站点最优化:三道小题分别考什么模型
鲜奶配送站点的最优化设置问题,是近几年数学建模竞赛里出场率很高的一道综合性赛题。很多人第一眼以为它只是算个最短路径,实际拆开看,这三道小题分别对应设施选址、集合覆盖、负载均衡三个完全不同的运筹学模型,每一问的评阅点都不一样。凡是拿到高分的队伍,都有一个共同习惯:先花半小时把 92 个网点的坐标、订奶量、配送道路数据读对,再谈算法创新。这篇笔记把这道题的考点分布、高频建模方法、真题答题思路和最容易丢分的细节拆开讲,适合准备参加数学建模竞赛的同学直接拿来对思路、背模型、练代码。
2. 考点分布:三问三模型,前置数据处理决定上限
2.1 题目考什么:从"最后一公里"到设施选址
赛题本身讲的是生鲜配送的"最后一公里"瓶颈。鲜奶配送站每天早晨要用非冷链小型物流车,把当天的鲜奶送到片区内各个网点。因为鲜奶保鲜要求高,配送在途时间必须尽量短,同时建站成本和配送成本也要平衡。题目给出 92 个网点的位置坐标、订奶量,以及网点之间的配送道路,要求回答三个递进的问题。
考点对应关系非常清晰:问题(1)是在 92 个网点中选择一个或多个点建配送站,目标是最经济,这属于设施选址问题,常用 p-中位模型或 p-中心模型;问题(2)加上配送时间不得超过 10 分钟的硬约束,同时考虑建设成本,这属于集合覆盖或最大覆盖问题;问题(3)要求各配送站点的配送量均衡,防止某个站配送量过低造成资源浪费、过高延误配送,这属于带容量约束的设施选址问题。
| 子问题 | 核心诉求 | 对应模型 | 常用求解手段 |
|---|---|---|---|
| 问题(1) | 总成本最低的建站方案 | p-中位模型 / 重心法 | 0-1 整数规划、枚举扫描 k |
| 问题(2) | 满足 10 分钟送达的最小建站数 | 集合覆盖模型 | 贪心初始解 + 整数规划 |
| 问题(3) | 配送量均衡的再优化 | 容量约束设施选址 | 均衡度指标 + 局部调整 |
这里要特别提醒备赛者:三道题不是三个孤立的小问,而是同一条逻辑链。问题(1)选出的站点集合,是问题(2)的初始候选集;问题(2)在时间约束下压缩出的站点集合,又是问题(3)做均衡调整的基础。评阅老师很看重这种递进关系,论文里如果没有体现联动,分数会明显受影响。
2.2 数据预处理:92 个网点、订奶量、道路表怎么读
这道题的数据部分通常包含三张表:网点坐标表、网点订奶量表、配送道路数据表。很多队伍跳过道路表,直接用坐标算欧氏距离,这是一个很常见的翻车点。题目明确给出了"网点之间的配送道路",说明实际配送必须沿着道路走,而不是直线飞过去。数据预处理的正确顺序应当是先读坐标、算距离矩阵,再通过道路表修正距离矩阵,得到真正的路网最短距离。
如果坐标表里 92 个网点中有部分网点之间没有直接道路连接,处理方式是用一个较大的惩罚值填充不可达项,再调用最短路算法求出每对网点之间的最短路径距离。这样得到的最短距离矩阵,才是后续所有模型的距离输入。杂交的做法是把道路数据扔掉、纯用坐标,评阅老师一眼就能看出数据没有用全,模型可信度直接打折。
2.3 第一步操作:用道路距离替换直线距离
下面这段代码演示了数据预处理的核心流程:先读坐标,计算欧氏距离作为兜底;再读道路表,把有道路连接的网点对的距离替换成实际路径距离;最后用弗洛伊德算法补全不可达路径。实际比赛中,可以把网点间距离存入二维数组D[j][k],后续所有模型都调用这个数组。
import pandas as pd import numpy as np # 读取坐标与订奶量 coords = pd.read_excel("附件_网点坐标.xlsx") # 字段:网点编号, x, y demand = pd.read_excel("附件_订奶量.xlsx") # 字段:网点编号, 订奶量 N = len(coords) xy = coords[["x", "y"]].values # 1. 先用坐标算欧氏距离,作为不可达时的兜底 D = np.zeros((N, N)) for i in range(N): D[i] = np.sqrt(((xy - xy[i]) ** 2).sum(axis=1)) # 2. 读入道路表,直接相连的网点对用道路距离覆盖欧氏距离 roads = pd.read_excel("附件_配送道路.xlsx") # 字段:起点编号, 终点编号, 道路长度 for _, r in roads.iterrows(): u, v, dist = int(r["起点编号"]), int(r["终点编号"]), r["道路长度"] D[u, v] = min(D[u, v], dist) D[v, u] = min(D[v, u], dist) # 3. 不可达的网点对设为一个大数,然后跑最短路补全 INF = 1e9 D[D == 0] = INF np.fill_diagonal(D, 0) for k in range(N): D = np.minimum(D, D[:, [k]] + D[[k], :])这段代码里最关键的是第二步:用道路长度去覆盖欧氏距离。min的作用是保留两者中更短的值,防止道路表中给出的长度反而比直线距离长导致异常。第三步的弗洛伊德算法复杂度为 O(n³),对 92 个网点来说完全跑得动,不需要换成更复杂的堆优化 Dijkstra。数据预处理做完后,先检查对角线是否为 0、不可达项是否仍为 INF,确认无误再进入建模。很多队伍后面模型解不出来,回头排查发现是最短路矩阵里残留了 0,导致所有网点距离都是 0,这个细节务必留意。
3. 问题(1):最经济建站方案——p-中位模型与成本曲线
3.1 为什么选 p-中位模型而不是重心法
问题(1)要求在 92 个网点中选择一个或多个点建设配送站,综合考虑配送速度、配送成本和建设成本,给出最经济的方案。这里有两个候选思路:重心法适合连续型选址,但本题配送站只能在网点位置上选,属于离散选址,所以重心法天然不合适;更贴合的是 p-中位模型,也就是从 N 个候选点中选 p 个设施,使所有需求点到最近设施的距离与需求量的加权和最小。
p-中位模型的核心逻辑是"每建一个站,覆盖一片区域,配送成本等于各网点订奶量乘以到所属配送站的距离"。建设成本体现为固定成本项,配送成本体现为距离加权项,两者之和就是总成本。这里需要强调一个考点:题里没有直接给出建设成本的具体数值,通常需要自行假设一个量级,并在论文里做敏感性分析。常见做法是设单站建设成本为一个常数 C,分别取 C 的不同量级,观察最终选址结果是否稳定。
3.2 0-1 整数规划怎么写:变量、目标、约束
用整数规划表达 p-中位模型时,需要定义两组决策变量:x[i]表示网点 i 是否建设配送站,取值 0 或 1;y[i][j]表示网点 j 是否由网点 i 处的配送站服务,取值 0 或 1。目标函数是建设成本与配送成本之和的最小化,约束条件有三个:每个网点必须分配给一个配送站;未建配送站的网点不能承接任何分配;资源约束即建站总数限制。
from pulp import LpProblem, LpMinimize, LpVariable, lpSum, LpBinary prob = LpProblem("p_median_model", LpMinimize) # x[i] = 1 表示在网点 i 建站;y[i][j] = 1 表示 j 由 i 配送 x = {i: LpVariable(f"x_{i}", cat=LpBinary) for i in range(N)} y = {(i, j): LpVariable(f"y_{i}_{j}", cat=LpBinary) for i in range(N) for j in range(N)} build_cost = [C] * N # 单站建设成本,C 需要按题目假设后做敏感性分析 dist = D # 上一步预处理得到的路网最短距离矩阵 weight = demand["订奶量"].values # 目标:建设成本 + 配送成本 prob += lpSum(x[i] * build_cost[i] for i in range(N)) + \ lpSum(y[i, j] * dist[i][j] * weight[j] for i in range(N) for j in range(N)) # 约束1:每个网点且只能由一个配送站服务 for j in range(N): prob += lpSum(y[i, j] for i in range(N)) == 1 # 约束2:只有建了站的位置才能提供服务 for i in range(N): for j in range(N): prob += y[i, j] <= x[i] # 约束3:建站总数不超过 p p = 5 prob += lpSum(x[i] for i in range(N)) <= p目标函数里dist[i][j] * weight[j]的含义是:网点 j 的订奶量乘以到配送站 i 的路网距离,等价于运输的吨公里成本。如果把配送成本视作与距离线性相关,这个目标函数就是总物流成本。约束 2 是一个典型的逻辑约束,它保证了服务关系只能指向已建站节点,如果没有这一条,整数规划会把所有网点分配到成本最接近 0 的未建站节点,结果完全失真。直接求解 92 个网点、92 个候选站的完整 0-1 规划,变量数会接近 8500 个,看起来吓人,但对 CBC 这类开源求解器来说规模并不大,通常几秒到几十秒就能出结果。
3.3 真题演练:扫描候选站数量 k 并画成本曲线
p-中位模型中 p 是未知数,题目没有限定建站数量,所以正确的答题思路是让 p 从 1 开始逐步增加到 10 或 15,每取一个 p 就求解一次最短路,记录对应的最小配送成本,再叠加建设成本,观察总成本曲线的变化趋势。总成本通常是先下降后上升的 U 形曲线,最低点对应最优建站数 k。这个"扫描 k 画成本曲线"的做法,是问题(1)最标准、最容易被评阅认可的呈现方式。
results = [] best_total = float("inf") best_p = None for p in range(1, 16): # 每次只需把上面整数规划里的约束3改为 x 的总和 == p # 求解后得到最小配送成本 delivery_cost 和建站数量 p total_cost = C * p + delivery_cost results.append((p, total_cost)) if total_cost < best_total: best_total = total_cost best_p = p print(f"最优建站数量: {best_p}, 最小总成本: {best_total:.2f}")扫描完成之后,论文里应当放一张表,列出 p 取 1 到 15 时各自的配送成本、建设成本、总成本,并标出最优方案。评阅老师最想看到的是你解释清楚为什么成本最低点是 p,而不是只贴一行结果。注意:如果建设成本 C 取得很小,成本曲线可能单调下降,最优解就是建满 92 个网点,这明显不合理,这时候需要回头调整 C 的量级,或者补充一个最低建站数量的限制。这类问题在赛题里通常不会给足参数,所以要把"参数假设 + 敏感性分析"写进论文,才算把这一问答完整。
4. 问题(2)与问题(3):10 分钟硬约束下的覆盖选址和配送量均衡
4.1 10 分钟限制的换算:3.33 公里半径
问题(2)新增了两个条件:配送站点到网点的配送时间不得超过 10 分钟,物流车时速为 20km/h。很多人在这里急着建模,却忘了先做单位换算。20km/h 等于每分钟 1/3 公里,10 分钟对应的最大配送距离约为 3.33 公里。也就是说,任何一个配送站,只能服务路网距离在 3.33 公里以内的网点。超过这个距离的网点不可达,必须在论文里明确说明这一点。
这里有两个细节值得注意:第一,距离必须用道路距离,不是直线距离,所以问题(2)的可行性判断要基于第 2 章预处理得到的最短距离矩阵;第二,3.33 公里半径是理论极限,实际配送还有装车时间、卸货时间、红绿灯等待,如果论文里能加一个"实际运行时间系数"(比如打 0.85 折扣),把有效半径压缩到约 2.8 公里,这个细节会让模型的工程合理性明显提升。这属于一类非常讨喜的加分操作,因为它体现的不是算法难度,而是对实际业务的理解。
4.2 集合覆盖的贪心初始解与整数规划最优解
在 3.33 公里的半径约束下,问题(2)的目标变成:至少建几个配送站,才能让所有 92 个网点都能在 10 分钟内被覆盖到。这是一个标准的集合覆盖问题。每个候选配送站都有一个"覆盖集合",也就是它 3.33 公里半径内能到达的全部网点。选择最少数量的配送站,使这些覆盖集合的并集等于全部网点。
求解集合覆盖最直观的方法是贪心算法:每次选择覆盖未覆盖网点数量最多的候选站,直到所有网点都被覆盖。贪心得到的站点数通常已经比较接近最优值,可以作为初始解,再用 0-1 整数规划验证最优性。
R = 3.33 # 10分钟配送半径,单位 km # cover[i] 为网点 i 作为配送站时能覆盖的网点集合 cover = [] for i in range(N): cover.append({j for j in range(N) if D[i][j] <= R}) # 贪心:每次挑能覆盖最多未覆盖网点的候选站 uncovered = set(range(N)) selected = [] while uncovered: best = None best_gain = -1 for i in range(N): gain = len(uncovered & cover[i]) if gain > best_gain: best_gain = gain best = i selected.append(best) uncovered -= cover[best] print("贪心选择的配送站:", selected) print("未覆盖网点数:", len(uncovered))贪心算法的优点是快,缺点是可能选到局部最优。比如有两个站点分别能覆盖 30 和 28 个网点,贪心先选第一个,结果第二个站点被迫与另一个站点大量重复覆盖,最终反而需要更多站点。所以贪心结果只能作为上界,真正的答题思路应当是:用贪心结果确定最优站点数的范围,然后固定 p 为这个值,再用集合覆盖的整数规划求解,验证是否存在更少的站数。论文中要把贪心和整数规划两者都写上,并注明"贪心作为初始启发式,整数规划负责最优性验证",这个表述非常加分。
4.3 配送量均衡:容量约束与均衡度指标
问题(3)是整个赛题里最容易答偏的一问。题目说的是:一个配送站的鲜奶配送量过低会造成资源浪费,配送量过高则会延误配送,需要重新给出考虑配送量均衡的最优配送站设置方案。注意题目没有给出每个配送站的容量上限数值,所以这一问的考点不是"在约束里写死一个容量值",而是"设计一个合理的均衡度指标,然后让模型去优化这个指标"。
常用的均衡度指标有三个:各配送站配送量的方差、变异系数、峰均比。峰均比定义为最大站配送量除以平均站配送量,越接近 1 表示越均衡。建模时,可以在问题(2)得到的站址基础上,重新对网点做分配,使得每个站的配送量尽量接近总配送量除以站数的平均值,同时保证 3.33 公里的时间约束不破坏。这个过程本质上是把原来的"就近分配"改成"带容量均衡的分配",属于容量约束设施选址问题。
4.4 真题演练:均衡度计算代码
第三问建议先做一次就近分配,算出每个站点的配送量,再用均衡度指标判断是否需要调整。下面这段代码演示了均衡度计算的全过程,先模拟一个分配结果,再输出各站配送量和两个核心指标。
import numpy as np # assignment[j] 表示网点 j 被分配到哪个配送站,编号来自问题(2)的选址结果 station_demand = np.zeros(len(selected_stations)) for j in range(N): station_demand[assignment[j]] += demand["订奶量"].values[j] max_load = station_demand.max() avg_load = station_demand.mean() cv = station_demand.std() / avg_load # 变异系数 cr = max_load / avg_load # 峰均比 print("各配送站配送量:", station_demand) print(f"峰值配送量: {max_load:.2f}") print(f"平均配送量: {avg_load:.2f}") print(f"峰均比: {cr:.2f}") print(f"变异系数: {cv:.2f}")如果峰均比超过 1.5,或者变异系数超过 0.3,就可以认为配送量明显不均衡,需要把高负荷站的某些网点重新分配给低负荷站。具体做法是:先对低收入的服务网店按需求量从大到小排序,依次尝试挪到仍满足 3.33 公里约束的邻近低负荷配送站,每挪一次就重新计算均衡度指标,直到指标进入合理区间。把这个过程写成伪代码放进论文,配合前后均衡度指标对比,第三问的答题完整度就很高。
5. 易错点:这道题最常丢分的五个细节
5.1 时间单位换算翻车:10 分钟不是 10 公里
错在哪:直接把"10 分钟"当作配送距离,或者把 20 除以 10 得出 2 公里,再或者干脆不换算,建模时用"时间"当"距离"。原因:车速 20km/h 这个条件给了,但容易被忽略。正确做法是先把 10 分钟换成 1/6 小时,再乘以 20km/h,得到 3.33 公里。任何用到这个数值的覆盖判断、半径设置、距离矩阵筛选,都必须以 3.33 公里为准。这类题只要结果表里出现超过 3.33 公里的配送弧段,评阅老师就知道换算错了。
5.2 忽略配送道路,直接把坐标当距离
错在哪:只用网点坐标算欧氏距离建模型,配送道路表完全没读。原因:坐标表看起来最直观,道路表读起来要额外处理,很多队伍图省事就跳过了。正确做法是必须先用道路表生成邻接矩阵,跑最短路,得到真正的路网距离。如果网点之间道路数据确实不完整,可以退一步用欧氏距离,但要在论文里明确说明"本文由于道路数据缺失,使用欧氏距离作为近似",这比默默忽略要诚实得多。只要道路表存在,就应当优先使用道路距离。
5.3 三问不联动,第三问从零开始做
错在哪:问题(1)自己建一套模型,问题(2)又从头来一遍,问题(3)再换一套方法,三个结果之间没有任何关系。原因:没意识到这三问是同一批数据的递进深化。正确做法是把问题(1)的选址结果作为问题(2)的候选站点集,问题(2)的站址作为问题(3)的初始分配基础。论文里建议画一张"模型递进关系"的说明,明确写出"问题(2)以问题(1)最优建站数为上界,问题(3)以问题(2)的站址为初始解做均衡调整",这一句话就能让评阅老师看明白你的整体思路。
5.4 均衡只做定性描述,不给量化指标
错在哪:在论文里写"各配送站配送量基本均衡""相差不大"就结束了,没有数据支撑。原因:很多队伍不知道怎么量化均衡,或者觉得图表能说明问题就不用写指标。正确做法是至少给出三个数值:各配送站配送量的列表、峰均比、变异系数。如果优化后峰均比从 2.1 降到了 1.2,这会成为第三问最有说服力的成果。没有量化指标的均衡分析,在评阅时几乎等同无效内容。
5.5 建站成本口径不清:总成本还是单站成本
错在哪:假设建站成本时,一会儿当作所有站点总预算,一会儿当作每个站点单独成本,导致目标函数数值乱套。原因:赛题没有直接给建设成本具体数值,需要自行假设,假设过程没有写清楚。正确做法是论文里明确规定:设单站建设成本为 C,总建设成本等于 C 乘以建站数量。然后对 C 取不同量级做敏感性分析,比如 C 取 100、500、1000 三档,观察最优建站数量是否变化。这样既避免了口径混乱,又主动做了评阅老师爱看的敏感性分析。
6. 答题技巧:一套可靠的建模交付流程
6.1 每道题的标准答题链
这道题的高分套路其实非常固定,每道小题都按"问题转化 → 模型选择 → 决策变量与目标约束 → 求解 → 结果表 → 敏感性分析"六步走。问题(1)用 p-中位整数规划加扫描曲线;问题(2)先把时间换成 3.33 公里半径,再用贪心加整数规划验证;问题(3)先算均衡度指标,再做容量均衡的重新分配。每一步都要在论文里说清楚"为什么选这个模型",而不是只说"我用了什么算法"。
答题时间上,建议按 3-2-1 分配:3 小时用在数据预处理和三个模型构建上,2 小时用于求解和画图,1 小时写论文核心部分。这道题建站成本参数是开放的,所以论文最后务必留 20 分钟做参数敏感性分析,这是拉分的关键。
6.2 论文呈现的三个硬要求
第一,结果表必须完整,每个问题的站点编号、覆盖网点数、配送量都要有列表或图表。第二,代码不能只贴不用,关键求解代码要配合文字说明决策变量的含义。第三,要做到三问结论联动:问题(2)的站址是问题(1)结果的收窄,问题(3)的均衡结果是问题(2)的优化。如果论文能明确写出这种递进,评阅老师会认为整篇文章是一个整体工程,而不是三道拼凑的题。
我从这道题里学到的最大教训是:数学建模竞赛高分的核心不是算法有多炫,而是数据读得全、单位换得对、模型选得准、结果讲得清。从那以后我每次拿到赛题,都会强制走一遍"单位换算 → 数据预处理 → 模型递进 → 量化指标 → 敏感性分析"的流程,五分钟内就能把题目拆完,后面的路就顺了。希望帮到你。
本文还有配套的精品资源,点击获取