
简介2026年华东杯数学建模竞赛B题医药物流安排问题的完整解决方案包面向参赛学生、建模爱好者及物流调度优化方向的开发者。资源围绕赛题提供全套参赛材料阶段化的赛题解析与思路分析文档详细梳理了背景、附件、子问题与难点核心求解代码实现了启发式算法、二优化局部搜索、模拟退火以及动态重规划策略并配有问题一至问题三的独立求解脚本、灵敏度分析与十五张高清图表生成模块。资源共五十一个文件压缩包约七点九四兆字节主要文件类型涵盖Python脚本、LaTeX论文源文件、PDF成品论文、PNG图表、JSON与CSV计算结果以及Markdown笔记目录按阶段与模块清晰划分便于按需取用。目前已有六十六人学习下载。从赛题拆解、候选方案对比、模型构建到结果评估与鲁棒性分析全流程均有文档与代码支撑论文已编译为PDF可直接用于复现、学习或作为竞赛写作参考。1. 医药物流B题在考什么一道带时效约束的库存-路径联合优化华东杯B题把场景定在医药物流实际考的是一道带时效约束的库存-路径联合优化问题。它比普通配送题多出来的东西是需求点收货时间窗、冷链温控和近效期管理任意一条没写进约束评阅人一眼就能看出方案不敢被医院用。应对思路很明确先判断需求是确定量还是随机量确定量走混合整数规划随机量加场景采样然后搭一套能跑通的中等规模算例最后把公式、代码、图表一一对应。这篇文章给出从模型到可运行代码再到论文组织的一条完整链路算例几分钟内能跑完适合比赛节奏。2. 把医药物流安排拆成数学模型变量、约束、目标三层缺一不可2.1 先分清“决策什么、已知什么”变量定义与算例设定拿到医药物流安排题第一件事是把题目里的业务名词翻成数学集合仓库、需求点、周期、车辆、药品类型。这五张表列完之后再问自己决策变量是哪几层。常见答案是三层——服务关系、运量、库存。服务关系是0-1变量表示“周期t里车辆k从仓库i出发服务需求点j”运量是连续变量或整数变量表示“从仓库i运往需求点j的第p类药有多少件”库存变量则记录仓库和各需求点周期末的剩余量。我一般先按中等规模固定模型行为再往题目给的规模上套。下面是一套比赛常用的算例设定后面所有代码都基于这套数据。为什么用中等规模因为医药物流题一旦出现“多周期多车型时间窗”模型规模会指数膨胀CBC这类开源求解器在完整算例上可能几个小时跑不完比赛时间不允许。先把小程序跑通、把结果讲圆再扩大规模是这类题最稳的推进方式。参数取值说明仓库数3配送中心各有容量上限需求点8医院/药店各有收货时限周期3对应三天或三个班次车辆64辆常温车 2辆冷链车药品类型2常温药 p0冷链药 p1需求量均值15方差3每个需求点每周期对每类药的需求坐标范围0~100欧式距离折算运输时间这套算例里我特意没建库存变量因为只有3个周期周期末剩多少药直接写一个累计表达式就能算没必要给每个仓库单独建库存状态。如果题目明确给了仓库容量约束或期初期末库存要求再把它显式建模也不迟。数据统一用字典和NumPy数组存不要散落在一堆Excel里。比赛要反复改参数做灵敏度分析数据文件和建模代码分开换算例时只改一个文件模型逻辑一行不动这个习惯能省下大量返工时间。2.2 医药物流和普通物流差在哪时效、温控、近效期三类硬约束医药物流题和标准车辆路径问题最本质的区别是三类硬约束。时效约束最常见的是“需求点有收货时间窗比如早8点到下午5点超过就拒收”。这个约束和路径优化天然冲突因为一旦车去完A点再去B点到达时间就是累加的。完整做法是引入车辆到达时间变量让车辆最早出发时间等于仓库开门时间再写一组递推关系车辆从i到j的到达时间等于上一节点的服务完成时间加行驶时间节点服务完成后才能去下一个点。写成线性约束就是 t_j^arr ≥ t_i^arr s_i d_ij - M(1-x_ij)其中M要求足够大又不能太大否则会拖垮求解器这个细节在第4章避坑里专门讲。代码算例里我把它简化成“需求点有一个最大可接受运输时限”任何服务这个点的车从仓库到它的运输时间不能超过阈值这样省掉一整组时间变量模型规模可控。温控约束比时效更隐蔽。冷链药品只能由冷链车运输这个规则如果只写成“冷链药必须上冷链车”往往漏掉另一半——“冷链车不能运常温药”。建模时两个方向都要写否则模型会派冷链车去送常温药造成运力浪费。简化做法是把车辆集合拆开冷链车索引和常温车索引分开然后加一组0-1约束常温车的冷链药运量强制为0。近效期约束是医药物流独有也是很多队伍丢分的地方。药品有剩余效期临期药要优先发到近距离需求点。最严谨的做法是给库存变量打批次标识但那样变量数量直接翻好几倍比赛时间根本撑不住。常见做法是设置一个“效期门槛”发往需求点的药品平均剩余效期不低于阈值或者给近效期药加一个“只能发往运输时间≤T_limit的需求点”的硬约束。后一种更好用因为它把风险从目标函数挪到约束里求解器不会为了省运输费把临期药发到远点。2.3 目标函数怎么写才对成本最小不是唯一答案很多队伍一上来就写总成本最小然后发现求解结果把远距离需求点全部砍掉——因为缺货惩罚没写。医疗场景下需求点缺药是大事所以目标函数要写成多个成本项的加权和运输成本、车辆启用成本、库存持有成本、缺货惩罚。权重代表着题目里定性描述的“重要程度”这是评阅人重点看的地方。目标项默认权重说明运输成本单位运量×距离单价随运量线性增长车辆启用成本每次出车80抑制无谓出车缺货惩罚每件500高于单趟运输成本数倍效期风险惩罚可选建议用约束替代如果题目明确要求“优先保证需求满足率”就把缺货惩罚系数调大比如让一次缺货的惩罚大于单日总运输成本模型自然会优先填满需求。还有一种写法是分层优化第一层最小化缺货总件数第二层在缺货方案最优的前提下最小化总成本。这种写法在论文里更好讲评阅人一眼就能看出你考虑了优先级缺点是代码里要跑两遍求解器时间上要留余量。题目没给优先级时用加权和就行权重怎么定、为什么这么定写进论文的“参数设置”一节本身就是加分项。3. 从模型到可运行代码Python PuLP 从建模到出图3.1 数据准备代码固定随机种子换算例只改一行先写数据准备段。比赛没给数据时自己生成一套并说明生成规则比空口讲模型强得多。关键点是固定随机种子这样每次运行结果完全一致论文里的图表能复现。import numpy as np # 固定随机种子保证每次跑同一套算例论文复现全靠它 rng np.random.default_rng(42) # 基础规模3仓库、8需求点、3周期、2类药品、6辆车 n_wh, n_cust, n_period, n_prod 3, 8, 3, 2 n_veh 6 # 坐标范围0~100距离直接折算成运输时间1单位距离1分钟 wh rng.uniform(0, 100, (n_wh, 2)) cust rng.uniform(0, 100, (n_cust, 2)) def travel_time(a, b): # 欧式距离作为仓库到需求点的运输时间 return np.sqrt(((a - b) ** 2).sum(axis1)) travel np.zeros((n_wh, n_cust)) for i in range(n_wh): travel[i] travel_time(wh[i], cust) # 需求每个需求点每周期对两类药的需求均值15、方差3截断到非负 demand rng.normal(15, 3, (n_cust, n_period, n_prod)) demand np.clip(demand, 0, None).round(1) # 需求点收货时限模拟医院签收时效超过这个时间的仓库不允许服务它 service_deadline np.full(n_cust, 55.0) # 运输成本单价仓库到需求点的距离成本随机取0.5~1.5 unit_trans_cost rng.uniform(0.5, 1.5, (n_wh, n_cust)) # 车辆容量前4辆常温车容量80后2辆冷链车容量60 vehicle_cap np.array([80, 80, 80, 80, 60, 60])这段代码的逻辑很直白先定规模再生成坐标和距离矩阵最后生成需求和成本参数。说明两点。第一随机生成需求时用 np.clip 把负数截断为0因为比赛数据里需求量不可能为负不截断会出现“负需求”这种荒谬输入。第二vehicle_cap 的数组顺序对应车辆索引索引0~3是常温车4~5是冷链车这个约定后面建模时直接用论文里也要写清楚。3.2 建模代码PuLP 的变量、约束与求解三段式PuLP 是 Python 里最常用的开源线性规划库底层默认带 CBC 求解器pip install pulp 就能用不需要额外装 Gurobi 的许可证。建模按三段走建变量、加约束、写目标。首先定义决策变量。import pulp prob pulp.LpProblem(Pharma_Logistics, pulp.LpMinimize) # 服务关系周期t内车辆k是否从仓库i出发服务需求点j0-1变量 x pulp.LpVariable.dicts(serve, ((i, j, t, k) for i in range(n_wh) for j in range(n_cust) for t in range(n_period) for k in range(n_veh)), catBinary) # 运量周期t车辆k从仓库i送往需求点j的第p类药品数量非负连续变量 q pulp.LpVariable.dicts(qty, ((i, j, p, t, k) for i in range(n_wh) for j in range(n_cust) for p in range(n_prod) for t in range(n_period) for k in range(n_veh)), lowBound0) # 缺货量需求点j在周期t对第p类药的未满足量进目标函数惩罚 short pulp.LpVariable.dicts(short, ((j, p, t) for j in range(n_cust) for p in range(n_prod) for t in range(n_period)), lowBound0)这里变量命名刻意和论文公式保持一致x 是服务关系、q 是运量、short 是缺货量。很多队伍论文里符号一套代码里另一套评阅人问起来对不上这是低级失误。运量用连续变量而不用整数变量是刻意取舍药品按件计本来是整数但整数变量会让求解难度成倍上升在容量约束下连续松弛的误差可以接受论文里写一句“运量按件数近似为连续变量”即可。然后是约束段。需求满足、车辆容量、服务时限、运量与出车关联、冷链限制五类约束一个都不能少。# 1) 需求满足送达量 缺货量 需求量 for j in range(n_cust): for p in range(n_prod): for t in range(n_period): prob (pulp.lpSum(q[i, j, p, t, k] for i in range(n_wh) for k in range(n_veh)) short[j, p, t] demand[j, t, p]) # 2) 车辆容量一辆车一个周期内总运量不超过车载容量 for t in range(n_period): for k in range(n_veh): prob (pulp.lpSum(q[i, j, p, t, k] for i in range(n_wh) for j in range(n_cust) for p in range(n_prod)) vehicle_cap[k]) # 3) 服务时限服务需求点j的所有车辆运输时间不超过其收货时限 for j in range(n_cust): for t in range(n_period): prob (pulp.lpSum(travel[i, j] * x[i, j, t, k] for i in range(n_wh) for k in range(n_veh)) service_deadline[j]) # 4) 运量与出车关联有运量才有服务关系防止“免费送货” for i in range(n_wh): for j in range(n_cust): for t in range(n_period): for k in range(n_veh): prob (pulp.lpSum(q[i, j, p, t, k] for p in range(n_prod)) vehicle_cap[k] * x[i, j, t, k]) # 5) 冷链限制常温车索引0~3不允许运输冷链药p1 for i in range(n_wh): for j in range(n_cust): for t in range(n_period): for k in range(4): prob q[i, j, 1, t, k] 0第4条约束是新手最容易漏的。如果不加它模型会输出“q0但x0”的解相当于车不出发却把药送到了纯属逻辑漏洞。加了这条运量和出车关系就绑定住了。第5条冷链限制直接写死比加惩罚项更干净也避免模型为了省成本让常温车硬送冷链药。目标函数和求解放最后。注意目标里三项权重要合理缺货惩罚远大于运输成本否则模型会选择牺牲需求点。fixed_cost 80.0 # 每辆次出车固定成本抑制无意义出车 prob (pulp.lpSum(q[i, j, p, t, k] * unit_trans_cost[i, j] for i in range(n_wh) for j in range(n_cust) for p in range(n_prod) for t in range(n_period) for k in range(n_veh)) fixed_cost * pulp.lpSum(x[i, j, t, k] for i in range(n_wh) for j in range(n_cust) for t in range(n_period) for k in range(n_veh)) 500.0 * pulp.lpSum(short[j, p, t] for j in range(n_cust) for p in range(n_prod) for t in range(n_period))) # 落盘lp文件方便排查约束gapRel设5%已经是比赛够用的精度 prob.writeLP(pharma.lp) prob.solve(pulp.PULP_CBC_CMD(msgTrue, gapRel0.05, timeLimit120)) print(状态:, pulp.LpStatus[prob.status]) print(目标值:, pulp.value(prob.objective))写 lp 文件这一步很多人跳过但它是排查的后悔药。求解无解或结果异常时用文本编辑器打开 pharma.lp逐条看约束有没有写错。gapRel 是 CBC 求解器的相对MIP间隙0.05 表示允许5%的优化误差对比赛足够想追更优解就把 gapRel 调到0.01代价是求解时间成倍增加。timeLimit 设120秒是为了防止模型卡死比赛现场时间就是分数不能在一个算例上耗一天。3.3 结果可视化代码图表直接搬进论文求解完之后不能只输出一个目标值就交差。把配送计划整理成表格把成本构成画成图这些直接进论文。下面这段代码把 x 变量的值读出来生成配送计划表并画成本堆叠柱状图。import pandas as pd import matplotlib.pyplot as plt # 读0-1解把值为1的配送关系整理成表格 rows [] for (i, j, t, k) in x: if x[i, j, t, k].value() and x[i, j, t, k].value() 0.5: rows.append({仓库: i 1, 需求点: j 1, 周期: t 1, 车辆: k 1}) plan_df pd.DataFrame(rows) print(plan_df) # 画各周期成本构成堆叠柱状图保存为PDF矢量图 periods range(1, n_period 1) trans_cost [] short_cost [] for t in range(n_period): trans_cost.append(pulp.value(pulp.lpSum( q[i, j, p, t, k].value() * unit_trans_cost[i, j] for i in range(n_wh) for j in range(n_cust) for p in range(n_prod) for k in range(n_veh))) or 0) short_cost.append(pulp.value(500.0 * pulp.lpSum( short[j, p, t].value() for j in range(n_cust) for p in range(n_prod))) or 0) plt.figure(figsize(8, 4)) plt.bar(periods, trans_cost, label运输成本) plt.bar(periods, short_cost, bottomtrans_cost, label缺货惩罚) plt.xlabel(周期) plt.ylabel(成本) plt.legend() plt.tight_layout() plt.savefig(cost_breakdown.pdf, bbox_inchestight)图表的作用不是装饰是支撑论文里的结论。配送计划表放“结果分析”一节展示模型确实生成了可用方案成本柱状图放“成本构成分析”说明缺货成本被压到了什么水平。导出 PDF 而不是 PNG是因为 PDF 是矢量图放大不模糊评阅人打印出来也清晰。图表数量不用多一张计划表、两张成本图、再加一张网络配送图够支撑整个实验章节。4. 医药物流模型避坑五个最容易翻车的现场4.1 时间窗一加就无解连错误提示都没有现象不加时间窗模型跑得通一加收货时间窗就报 Infeasible求解器只说“无解”不给具体原因。原因最常见的是车辆最早出发时间设成了0而需求点最早收货时间是早上8点仓库到需求点的运输时间可能只有20分钟车凌晨就到了但需求点没开门时间窗直接冲突。解决两个方向都处理。要么给车辆加一个“允许等待”的状态变量要么把时间窗从硬约束改成软约束通过惩罚项让模型自己权衡。比赛优先选软约束硬约束无解时排查成本太高。4.2 加了子回路消除约束求解时间从分钟变几小时现象模型带路径优化时去掉子回路约束能跑加了 MTZ 子回路消除之后求解时间暴涨甚至超过 timeLimit。原因MTZ 约束里用的大M值取得太大。很多人一上来就设 M10000导致线性松弛质量极差分支定界累到崩溃。解决把 M 收紧到“最晚到达时间 最大服务时间”比如 M500要么干脆换成 DFJ 子回路消除用小规模残缺约束迭代添加。对比赛算例靠缩 M 是最省事的。4.3 冷链车空跑常温车却在送冷链药现象检查结果发现冷链车大部分时间空载常温车却承担了冷链药的运输。原因建模时只写了“冷链药必须由冷链车运”漏了“冷链车只能运冷链药”这一半约束模型发现常温车不够用就绕过冷链车去送反正没有约束拦住它。解决把两个方向都写进模型。代码里我用的是“索引0~3的常温车冷链运量强制为0”这就是补上了另一半。如果不补冷链车被浪费目标值虚高结果根本不能用。4.4 近效期药被发到最远的需求点现象加了效期风险惩罚后临期药反而被分配到远距离需求点运输成本飙升。原因目标函数里惩罚的是“效期风险”但没限制“运输时间”模型为了省车辆启用成本宁可把临期药塞给远点然后减少一车次因为省下的固定成本和运输费大于效期惩罚。解决把“近效期药只能发往运输时间≤T_limit 的需求点”写成硬约束而不是在目标函数里加惩罚项。硬约束直接掐掉不可行解模型没有钻空子的余地。4.5 论文公式和代码对不上评阅人一问就露馅现象论文里写的约束符号和代码里的变量名完全对不上评阅人顺着公式找代码找半天找不到。原因写论文时公式重排过代码没同步修改这是团队协作最常见的问题。解决建模变量名和论文符号保持一致论文里 x_{ijtk} 代码里就叫 x[i][j][t][k]不要论文里叫 u、代码里叫 v。再把 lp 文件留档注释里标注“此约束对应论文式(12)”评阅人追问时能当场指出来。5. 把结果写成完整论文灵敏度分析、图表编号与一个调参技巧论文组织结构按这个顺序写最省力问题重述、模型假设、符号说明、模型建立、求解算法、算例分析、灵敏度分析、结论。模型假设那里的篇幅不要超过一页但每条假设都要能自圆其说比如“运量按连续变量处理”“允许拆分配送”“不考虑车辆故障”。评阅人看模型假设其实是看你是不是回避了关键难点回避了但写明白了比没写强。灵敏度分析是区分二等奖和一等奖的分水岭。不要只做“调参-记录目标值”的重复劳动要针对题目里最敏感的因素做扰动。我一般做三组需求量上下波动20%、可用车辆数增减1辆、收货时限放宽或收紧1小时。每组扰动至少跑5次取均值和标准差画误差棒折线图。这张图放进论文能直接支撑“模型在需求波动下仍然保持较高满足率”这种结论。最后一个调参技巧也是我自己踩坑踩出来的经验CBC 的 gapRel 不要一味追求小。0.05 的间隙对比赛完全够用0.01 会多花5到10倍时间解的质量提升却常常不如随机换一组数据来得明显。如果模型在完整算例上跑不动先把时效软约束放开一轮拿到一个可行解兜底再逐步加约束找回精度。这个“先松后紧”的迭代顺序比任何求解器参数都管用。我做这类题最深的教训是先跑通一个简化版再往里面加花活。最崩溃的一次是比赛最后一晚发现冷链车全在空跑就是因为当时只写了半条冷链约束。从那以后每次建模我都把“约束的每个方向是否写全”当作检查清单的第一项。这个习惯让我少翻了很多次车。数据种子固定、lp文件留档、变量名和论文符号一致这几件小事做好了整篇论文的置信度都会上一个台阶。希望帮到你。本文还有配套的精品资源点击获取