数学建模国赛C题解析:供应链优化中的订购与运输策略 1. 赛题核心与破题思路总览2021年的国赛C题题目是“生产企业原材料的订购与运输”这题一出来很多队伍就懵了。它不像A题那样有明确的物理模型也不像B题那样偏向数据分析C题更像是一个披着数学外衣的运筹优化与管理决策问题。说白了就是给你一家企业的历史数据让你去琢磨它该怎么买材料、怎么运材料才能既不断供又不多花钱。听起来像是企业管理课上的案例但要用数学建模的语言把它说清楚、算明白这里面门道就深了。这道题的核心在我看来是在不确定性中寻找确定性的决策规律。数据里充满了噪声和波动比如供应商的供货量每周都在变企业的产能和消耗也并非一成不变。我们的任务不是简单地拟合历史曲线而是要从这些看似杂乱的数据中提炼出影响订购与运输的关键因素并构建一个能指导未来行动的决策模型。这要求我们具备三种能力一是从业务角度理解数据内涵的能力二是将现实约束转化为数学语言的能力三是设计高效求解算法的能力。很多队伍折在第一步对着数据表格不知从何下手或者困在第二步模型建得无比复杂却无法求解又或者倒在第三步得到一个理论上很美但实际无法操作的“最优解”。这道题适合有一定编程基础尤其是Python或MATLAB、对运筹学或数据分析感兴趣的同学。它不要求你有多高深的数学理论但非常考验你的综合应用能力和解决实际问题的思维。接下来我会把自己当时解题和后来复盘的心得拆解成几个核心部分希望能帮你理清思路避开那些我当年踩过的坑。2. 问题拆解与模型构建的逻辑框架面对一个多阶段、多变量的决策问题最忌讳的就是想着一口吃成胖子试图建立一个“大一统”模型解决所有问题。我们必须采用分而治之的策略将原问题分解为几个逻辑上递进、又相对独立的子问题。2.1 第一阶段数据驱动的需求与供应特征分析这是所有工作的基石也是论文第一个模型的价值所在。题目给了过去五年的数据包括402家供应商的供货特征供货量、供货量方差和企业的原材料消耗量。这一步的目标不是预测而是刻画。首先是对供应商进行画像与分类。你不能把402家供应商一视同仁。一个每周稳定供应100吨的供应商和一个平均供应100吨但波动极大的供应商对企业来说风险是天差地别的。这里就需要用到聚类分析比如K-Means或层次聚类。特征维度怎么选我建议至少考虑三个历史平均供货量反映规模、供货量的变异系数CV标准差/均值反映稳定性、以及供货量为0的周数占比反映可靠性。通过聚类我们可以把供应商分为几类例如“稳定大客户型”、“稳定小批量型”、“波动风险型”、“极不可靠型”。这个分类将为后续的订购策略提供根本依据。注意很多同学在这里直接使用原始供货量数据做聚类效果很差。因为数据量纲和数量级差异大必须进行标准化处理如Z-score。另外聚类数目K的选择不能拍脑袋要用肘部法则Elbow Method或轮廓系数Silhouette Coefficient来辅助确定。其次是对企业需求的分析。企业的周消耗量数据需要分解出趋势性、季节性和随机性成分。可以使用时间序列分解如STL分解或简单的移动平均来分析。重点要回答需求是否有明显的长期增长趋势是否有以年为周期的季节性波动比如某些季度生产任务重需求的波动性方差有多大这个分析结果将直接决定你后续预测模型的选择和安全库存的设置。2.2 第二阶段订购策略模型——与不确定性共舞这是整个赛题的核心。订购决策面对的核心矛盾是订多了库存成本高、资金占用大订少了面临缺货停产的风险损失更大。这是一个典型的报童模型Newsboy Model思想的扩展应用但比经典报童模型复杂得多因为“供应商”不止一个而且每个都“不靠谱”。模型建立的关键在于转换视角。不要直接去建模“每周向每个供应商订多少”这个维度太高几乎无法求解。应该建模的是“向每一类供应商采购的比例或优先级”。例如我们可以定义一个决策变量从“稳定大客户型”供应商处采购的原材料应占总采购量的百分之多少。这样决策变量的维度就从402维降到了聚类数比如4维变得可解。目标函数如何设定最小化总成本是自然的想法。总成本应包括1) 原材料购买成本2) 库存持有成本与库存量成正比3) 缺货损失成本这是最关键的通常远高于前两者。缺货成本很难直接估算可以将其转化为一个约束条件要求缺货的概率低于某个阈值例如5%。这样问题就变成了“在满足一定服务水平不缺货概率的前提下最小化购买与库存成本”。约束条件怎么列除了上面提到的服务水平约束还必须考虑每家供应商的供应能力上限可根据历史最大供货量估算企业的仓储容量上限以及最重要的——供应商的供应不确定性。我们需要用数学来描述这种不确定性。一种实用的方法是随机规划Stochastic Programming或鲁棒优化Robust Optimization。例如假设供应商的供货量服从其历史均值和方差的正态分布当然需要检验正态性假设那么我们的订购量决策必须使得在绝大多数可能的供货情景下例如95%置信区间都能满足生产需求。这就会导出一组包含概率的约束条件。实操心得完全精确地求解这个随机规划模型可能非常耗时。一个在比赛中非常有效的简化方法是情景分析法Scenario Analysis。我们可以根据历史数据蒙特卡洛模拟生成成百上千种未来一周可能的“供货情景组合”每个供应商的供货量在其历史分布中随机抽取。然后我们的订购策略要使得在这所有模拟情景中的绝大多数比如95%里都不发生缺货。这实际上是把概率约束转化为了大量确定性约束可以用线性规划或整数规划来近似求解在MATLAB或Python的PuLP、Gurobi等工具中实现起来更直接。2.3 第三阶段运输策略模型——路径与成本的博弈订购量确定后运输问题相对独立。但题目妙就妙在运输成本与运输量不是简单的线性关系而是存在显著的“规模经济”——单次运输量越大单位重量运输成本越低。这涉及到车辆路径问题VRP或更简单的装载问题的变体。核心决策点有两个一是运输频次每周运几次二是运输路径如何组合不同供应商的货物进行联合运输。目标是最小化运输总成本。一个实用的建模思路是分两步走确定经济运输批量根据运输公司的报价表固定运费里程运费可以计算出一个“经济运输批量”的区间。单次运输量低于这个区间单位成本会很高接近或超过这个区间单位成本会降低。可以建立一个简单的成本函数求解出使单位运输成本最低的推荐运输量。供应商聚类与路径规划根据第一步确定的经济运输量以及各类供应商的地理位置题目隐含了位置信息如供应商编号可能隐含地区将地理位置相近的、且订购量累计能达到经济运输批量的供应商组合在一起安排同一辆车进行巡回运输。这可以转化为一个带容量约束的聚类问题或者小规模的VRP问题。对于国赛规模使用启发式算法如节约算法 Clarke-Wright就能得到很好的可行解不必追求精确最优。这里有一个巨大的坑点题目中运输成本是“一家企业承担”这意味着你不能简单地把所有供应商的货物混在一起算总账。你必须考虑如果为A、B两家供应商合运总成本比分开运要低那么节省的成本如何在A、B之间分摊这涉及到合作博弈中的成本分摊问题如Shapley值。虽然深入探讨这个会增加模型复杂度但在论文中提及这个思考能显著提升模型的深度和亮点。3. 模型求解与算法实现的实战细节思路清晰了能不能算出来是另一回事。C题对编程实现的要求不低下面我分享一些具体的求解工具和技巧。3.1 数据分析与预处理阶段的代码要点数据预处理是保证后续所有模型正确的关键。使用Python的Pandas库是首选。import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 读取数据 supply_data pd.read_excel(附件1 供应商的供货数据.xlsx) consume_data pd.read_excel(附件2 企业的原材料消耗量.xlsx) # 分析供应商计算每个供应商的平均供货量、标准差、变异系数、缺供周数 supply_summary supply_data.groupby(供应商ID).agg( 平均供货量(供货量, mean), 供货量标准差(供货量, std), 缺供周数(供货量, lambda x: (x 0).sum()) ).reset_index() supply_summary[变异系数] supply_summary[供货量标准差] / (supply_summary[平均供货量] 1e-5) # 防止除零 # 数据标准化 scaler StandardScaler() cluster_features scaler.fit_transform(supply_summary[[平均供货量, 变异系数, 缺供周数]]) # 使用肘部法则确定K值 inertia [] for k in range(2, 11): kmeans KMeans(n_clustersk, random_state42) kmeans.fit(cluster_features) inertia.append(kmeans.inertia_) plt.plot(range(2, 11), inertia, markero) plt.xlabel(Number of clusters (K)) plt.ylabel(Inertia) plt.title(Elbow Method For Optimal K) plt.show() # 假设通过看图确定K4 kmeans KMeans(n_clusters4, random_state42) supply_summary[聚类标签] kmeans.fit_predict(cluster_features)3.2 订购策略模型的求解从随机规划到情景模拟如前所述直接求解随机规划模型较难。我们采用情景模拟法。import numpy as np from scipy import stats import pulp # 线性规划求解库 # 假设我们已确定向4类供应商采购的比例为决策变量 x1, x2, x3, x4 # 总需求为D根据预测模型得到 # 模拟生成N1000种供货情景 N 1000 np.random.seed(42) scenario_supply np.zeros((N, 4)) # N种情景每类供应商的供货量 # 为每一类供应商根据其历史数据拟合分布这里假设正态并生成随机样本 # 假设已计算出每类供应商供货量的均值向量 mean_supply 和协方差矩阵 cov_supply # 使用多元正态分布生成相关的情景更符合现实因为供应商的供货可能相关 mean_supply np.array([...]) # 4维均值 cov_supply np.array([[...]]) # 4x4协方差矩阵 scenario_supply np.random.multivariate_normal(mean_supply, cov_supply, N) # 将供货量小于0的情景截断为0物理意义 scenario_supply np.maximum(scenario_supply, 0) # 建立线性规划问题最小化总成本购买成本库存成本 prob pulp.LpProblem(Material_Ordering, pulp.LpMinimize) # 决策变量每类供应商的采购比例 x [pulp.LpVariable(fx{i}, lowBound0, upBound1) for i in range(4)] # 总采购比例之和为1 prob pulp.lpSum(x) 1 # 目标函数假设购买单价相同主要考虑库存成本。设单位库存成本为h总需求为D。 # 则期望库存 期望采购量 - D D * sum(期望比例) - D 0不对。 # 更准确的对于每种情景s实际到货量 sum(x_i * 情景s中i类供应商的供货量) # 库存 到货量 - D (若为正)缺货 D - 到货量 (若为正) # 我们需要引入辅助变量但这会使变量数激增N个情景每个都要两个变量。 # 另一种更简单实用的方法将缺货概率约束转化为情景约束。 # 定义缺货情景的容忍度比如允许5%的情景缺货 alpha 0.05 # 我们需要一个约束在至少 (1-alpha)*100% 的情景下到货量 D # 这等价于对于排序后的到货量第 (alpha*N) 个最小值即5%分位数 D # 这无法直接表示为线性约束。一个常用的保守近似是 # 为每个情景添加一个0-1变量表示是否缺货然后约束缺货情景总数 alpha*N。 # 但这引入了大量整数变量求解困难。 # 因此比赛中更实用的方法是直接要求“期望到货量 D * (1 安全系数)” # 安全系数可以通过需求波动的标准差来确定。 # 这是一个确定性约束简单易处理。 demand_mean ... # 预测的周需求均值 demand_std ... # 预测的周需求标准差 safety_factor 1.645 # 对应95%的服务水平假设正态 prob pulp.lpSum([x[i] * mean_supply[i] for i in range(4)]) demand_mean safety_factor * demand_std # 求解 prob.solve(pulp.PULP_CBC_CMD(msgFalse)) print(Status:, pulp.LpStatus[prob.status]) for v in prob.variables(): print(v.name, , v.varValue)3.3 运输策略模型的启发式求解对于运输路径规划可以实现一个简单的节约算法。# 假设有一个供应商列表包含ID坐标(x,y)货物重量weight suppliers [...] # 以及一个配送中心坐标 depot depot (0, 0) # 车辆容量 capacity def distance(a, b): return np.sqrt((a[0]-b[0])**2 (a[1]-b[1])**2) # 初始化每个供应商单独一辆车往返 routes [[depot, s, depot] for s in suppliers] # 计算所有可能合并路线i的终点到j的起点所节约的距离 savings [] for i in range(len(suppliers)): for j in range(i1, len(suppliers)): # 节约值 原距离(Di0 D0j) - 新距离(Dij) saving distance(depot, suppliers[i]) distance(depot, suppliers[j]) - distance(suppliers[i], suppliers[j]) savings.append((saving, i, j)) # 按节约值从大到小排序 savings.sort(reverseTrue, keylambda x: x[0]) # 合并路线 for saving, i, j in savings: # 找到包含供应商i和j的路线且它们不在同一条路线中且合并后重量不超过容量 route_i find_route_containing(routes, i) route_j find_route_containing(routes, j) if route_i is not route_j and get_total_weight(route_i) get_total_weight(route_j) capacity: # 合并两条路线具体连接逻辑需仔细处理如首尾连接 new_route merge_routes(route_i, route_j, i, j) routes.remove(route_i) routes.remove(route_j) routes.append(new_route) # 最终routes即为近似最优的运输路径4. 论文写作要点与常见失误规避模型建得好不如论文写得好。国赛评阅时间短评委第一眼看到的就是你的论文。以下几点是决定你能否从众多队伍中脱颖而出的关键。4.1 摘要浓缩的必须是精华摘要占论文评分权重极高必须字斟句酌。一个好的摘要结构应该是用一两句话概括问题背景与本质。例本文研究了一类在供应不确定与需求波动下的多周期原材料订购与运输决策问题。逐段简述针对每个子问题你所做的工作。要明确写出“针对问题一我们建立了基于XXX聚类和YYY分析的供应商评估模型……”、“针对问题二我们构建了以最小化期望总成本为目标、以缺货风险为约束的随机规划模型并采用情景模拟法进行求解……” 每个子问题对应一两句话说明用了什么方法、得到了什么核心结论例如供应商分为4类主要向稳定型供应商采购等。总结全文亮点与特色。例本文的特色在于将复杂的随机优化问题转化为可处理的情景模拟线性规划创新性地考虑了运输规模经济下的成本分摊问题等。给出关键量化结果。把你们算出来的最核心的指标放进去比如“最终方案使得未来24周的期望总成本降低了约15%”这非常提气。致命错误摘要中出现“我们通过努力”、“我们查阅了大量资料”等空洞表述或者只说了过程没写结论或者篇幅过长超过一页。4.2 模型假设平衡合理性与简洁性假设是模型的起点既要贴近现实又要便于建模。C题中一些合理且必要的假设包括供应商未来的供货特征均值、波动性与其历史表现一致。企业的生产需求在短期内如未来24周保持与历史相似的趋势和季节性模式。运输车辆容量充足且运输成本函数由题目所给报价表确定不考虑动态油价、交通拥堵等。库存持有成本与库存量成正比缺货成本高昂可转化为服务水平约束。注意事项避免写出明显不合理或过于强硬的假设如“假设所有供应商供货量严格服从正态分布”。可以写成“为简化分析我们假设供应商供货量的不确定性可用正态分布近似描述该假设可通过后续的分布拟合检验进行验证”。这体现了你的严谨。4.3 模型检验与灵敏度分析体现深度这是区分普通论文和优秀论文的关键。模型建完了不能只说“结果合理”。稳定性检验改变聚类数目K如3,4,5观察最终的订购策略和总成本是否发生剧烈变化。如果变化不大说明你的模型是稳健的。参数灵敏度分析改变关键参数看结果如何变化。例如逐步提高服务水平要求从95%到99%观察总成本如何增加。或者改变运输成本函数中的固定费用分析其对运输频次决策的影响。用图表清晰地展示这种变化关系并给出业务解释如“服务水平的边际提升会导致成本加速上升因此企业需在服务与成本间权衡”。与简单策略对比将你们复杂的优化模型结果与简单的策略对比比如“均匀订购策略”向所有供应商平均采购或“历史平均策略”按历史平均比例采购。计算出成本节约的百分比这是模型价值最直观的体现。4.4 常见失误与避坑指南根据多年评审和辅导经验以下是C题参赛队最容易翻车的地方问题理解片面化只做预测不做优化。把题目做成了单纯的需求预测和供应商供货量预测然后简单地把预测值相加。完全忽略了“决策”这个核心没有考虑在不确定性下的最优选择。模型过度复杂化试图建立一个包含所有细节的完美模型比如用深度学习预测每个供应商下周的精确供货量再用动态规划求解多阶段决策。结果模型根本解不出来或者求解时间过长最后只能给出一个粗糙的演示。记住在国赛三天里一个能求解并给出明确结果的简单模型远胜于一个无法求解的复杂模型。忽略运输的规模效应将运输成本简单地处理为与运量成正比的线性关系完全忽略了题目中给出的阶梯报价表所体现的规模经济。这会导致运输策略部分严重失分。论文变成代码说明书通篇在讲“我们用了Python的sklearn库的KMeans函数”却没有讲清楚为什么用KMeans、特征为什么选这三个、聚类结果如何解释。评委想看的是你的数学建模思想而不是编程操作手册。代码应放在附录正文用数学公式、流程图和表格来说明你的模型。结果分析空洞只给出“我们得到了以下结果”然后贴上一堆表格。必须对结果进行解读例如“从图3可以看出我们模型推荐的策略将采购集中度提高了20%主要集中于I类稳定型供应商这有效降低了供应风险。”、“表4显示当需求波动增加10%时安全库存需要提高15%总成本相应上升3.5%说明系统对需求波动较为敏感。”最后想说的是数学建模竞赛比拼的不仅仅是数学和编程能力更是在有限时间内解决一个开放问题的系统能力。从审题、拆解、建模、求解到写作每一步都需要清晰的逻辑和果断的取舍。2021年C题是一个绝佳的练手题它没有标准答案但只要你的模型逻辑自洽、求解可行、结果有据、表述清晰就一定能获得不错的成绩。多从“如果我是企业采购经理我需要什么样的决策支持”这个角度去思考你的模型就会更有生命力。