资讯动态

数学建模国赛优秀论文复现全流程:选址优化实战

发布时间:2026/9/2 23:18:18 来源:尧图企业网站定制
如果你正在备战 2026 年数学建模国赛一定听过这样的建议要想稳拿国奖先把往年国赛优秀论文吃透。但“吃透”到底怎么操作是读完画个重点还是把论文里的模型自己敲一遍我在备赛和复现优秀论文的过程中发现大多数队伍真正卡住的地方不是论文看不懂而是不确定自己有没有真正看懂。看懂的标志不是“觉得作者说得有道理”而是能把论文里的符号、公式、算法变成能运行的代码并且得到与论文一致或可解释的结果。这篇文章就用一套完整的方法论带你把 2025 年国赛 B 题优秀论文的系统复现流程走一遍同时把建模、求解、可视化、排错的完整链路梳理清楚。内容偏实操适合准备参赛的本科生、研究生也适合想提升建模与代码能力的同学参考。1. 背景与核心概念1.1 优秀论文复现是什么优秀论文复现就是按照获奖论文的建模思路从题目原始数据出发重新建立数学模型、编写求解代码、完成结果可视化最终得到与原文相近的结果或结论。这个过程不是简单抄一遍代码。很多优秀论文附录里并没有完整代码获奖者提交的通常是结果和图表所以复现时往往需要自己补全很多细节。比如数据预处理怎么做、距离矩阵怎么算、约束条件怎么转化、求解参数怎么调这些都需要你根据论文描述去推理和补全。复现的最高价值是你被迫进入作者的思考过程。你不是在看“结果”而是在重新经历一次从题目到模型的推导过程很多论文里一笔带过的假设、简化和取舍只有在你动手实现时才会真正暴露出来。1.2 为什么复现是备战国赛的高效方式备赛方式有很多种刷题、看书、看网课、组队模拟但复现优秀论文的投入产出比非常突出。原因有三个第一优秀论文本身就是高质量题库。每一篇国赛优秀论文背后对应的都是一道经过命题组反复打磨的赛题数据、背景、约束条件都有明确的工程或管理场景比单纯做书后习题更贴近真实竞赛。第二复现能同时训练三大能力读题解题能力、建模能力、代码实现能力。这三个能力恰好决定了你在竞赛三天里能不能出成果。第三复现过程中积累的代码模块可以复用。比如数据标准化、距离计算、线性规划求解、敏感性分析、图表绘制这些代码经过一次打磨后在后续比赛中可以快速迁移。竞赛只有三天很多队伍输在“代码从零开始”而不是“建模能力不够”。1.3 复现与抄袭的区别这里必须强调学术规范。复现优秀论文的目的是学习建模框架与算法思路绝对不等于直接照抄获奖论文的结构、措辞和结论再去参赛。真正的复现式学习要求你在读论文后关闭原文用自己的语言重新描述问题用自己的代码重新实现模型。如果你能写出代码并运行出结果说明你掌握了如果你只是把原文图表换了个颜色放进自己的论文那是学术不端一旦被查实后果非常严重。从备赛策略来看复现优秀论文的最佳产出形态也应该是“自己的代码 自己的笔记 自己的总结”而不是“复制粘贴的论文模板”。2. 环境准备与工具链在动手复现之前先把工具链准备好。如果你参加过数学建模竞赛大概率已经装过 Python 环境没有的话建议直接安装 Anaconda它会一次性带上 Jupyter Notebook 和大部分常用科学计算库对于快速迭代建模方案非常方便。2.1 Python 基础环境本文示例以 Python 3.9 为例操作系统不限Windows、macOS、Linux 均可运行。建议使用独立的虚拟环境避免不同项目之间包版本相互影响。# 创建虚拟环境终端执行 conda create -n b2025 python3.9 conda activate b2025如果你习惯用 venv也可以python -m venv b2025 # Windows 激活 b2025\Scripts\activate # macOS/Linux 激活 source b2025/bin/activate2.2 核心库与求解器复现 B 题这类优化规划类问题常用库如下库名用途安装方式numpy数值计算、矩阵运算pip install numpypandas读取 csv/excel、表格数据处理pip install pandasmatplotlib结果可视化、绘图pip install matplotlibscipy线性规划、插值、统计pip install scipypulp线性规划/整数规划建模与求解pip install pulp其中pulp是本文重点使用的优化建模库。它会把数学模型翻译成标准线性规划形式然后调用内置求解器 CBC 求解。如果你的模型规模很大或者需要商用求解器如 Gurobi也可以把pulp求解器切换为对应的求解器类型接口基本不变。需要注意版本问题scipy.optimize.linprog从 1.9 版本开始才支持整数变量如果你习惯用scipy做规划建议把scipy升级到较新版本如果不想纠结版本直接用pulp会更省心。2.3 示例项目结构建议在复现每一篇优秀论文时都建立独立目录结构如下B2025_reproduction/ ├── data/ │ ├── demand.csv │ └── candidate.csv ├── src/ │ ├── data_preprocess.py │ ├── model_solve.py │ ├── visualize.py │ └── sensitivity.py ├── output/ └── requirements.txtdata放原始数据或生成数据src放代码output放图表与结果表格。这样做的最大好处是训练结束一个月后你再回来看这批代码依然能快速恢复上下文。3. 优秀论文复现的完整流程复现一篇论文绝对不是打开论文从头读到尾然后pip install xxx开始写代码。没有章法的复现容易卡在中间某个环节最后不了了之。3.1 读论文的三个层次我把读论文分成三个层次你可以对照自己当前处于哪个阶段。第一层是浏览层。快速阅读摘要、问题分析、模型建立、结果分析这些核心部分搞清楚这篇论文做了什么模型、用了什么算法、得到什么结论。这一层能回答“题目大概怎么做”的问题。第二层是结构化层。把论文中的问题背景、模型假设、符号定义、目标函数、约束条件、算法流程、结果图表梳理成自己的一份笔记。这一层能回答“论文到底做了哪些具体事情”。第三层是批判层。带着怀疑去读比如“为什么这里用整数规划而不是线性规划”“这个假设在真实场景中是否成立”“如果数据量增大 10 倍算法还能跑吗”。这一层能回答“这篇论文好在哪哪里可以改进”。很多同学读论文只停留在第一层甚至读完只觉得“写得真好”却说不出好在哪里。复现要求你至少到第二层最好能到第三层。3.2 建立符号表与假设清单复现第一步不是写代码而是先把论文中的数学符号整理成表格。比如论文中出现了(x_j)是否在第 (j) 个候选点建立服务设施(y_{ij})需求点 (i) 分配给服务点 (j) 的数量(f_j)第 (j) 个候选点的固定建设成本(c_{ij})需求点 (i) 到候选点 (j) 的单位运输成本(d_i)需求点 (i) 的需求量(cap_j)第 (j) 个候选点的服务容量你要把这些符号统一整理为代码变量名建议保持一一对应关系。比如 (x_j) 对应x[j](y_{ij}) 对应y[(i, j)]这样后期排查问题时会非常方便。同时把论文中的所有假设列成清单。比如“每个需求点可以被多个服务点共同服务”和“每个需求点只能被一个服务点服务”这两个假设对应的模型完全不同前者是连续分配问题后者是单源选址问题。漏掉任何一个假设复现出的模型可能与原文完全不同。3.3 从公式到代码的翻译原则数学公式和代码之间没有天然的一一对应关系但可以遵循以下原则第一先翻译目标函数再翻译约束条件。目标函数告诉你“要优化什么”约束条件告诉你“在什么限制下优化”顺序不能反。第二每个约束都要对照符号表确认变量维度。比如求和符号 (\sum_j) 对应的是对候选点下标遍历写成代码就是for j in range(m)。第三线性规划的矩阵形式与建模语言形式要能互相转换。如果论文给了标准形式 (min c^Tx)你要能写出对应的pulp或scipy代码反之看到代码也能把它还原成数学表达式。3.4 数值验证与误差分析复现完成不代表结束。你需要问自己三个问题模型运行结果在数量级上是否合理关键结论是否和论文一致如果不一致差距来自数据、参数、约束还是求解精度在复现过程中结果与论文不一致是常态不要一上来怀疑论文有问题。最常见的差异来源包括距离单位不统一、坐标未归一化、容量约束方向写反、目标函数缺了某个成本项。每次调试后记录原因和修正方式这部分笔记到你正式比赛时就是最宝贵的排错手册。4. 实战案例典型 B 题优化类问题复现下面用一道典型 B 题风格的优化规划问题来演示完整复现过程。这类问题的特征是有明确的决策变量比如在哪建、建多少、怎么分配有成本或效益目标有资源约束最终结果要求给出可落地的方案。4.1 案例选题与建模思路为了演示方便这里设计一个“应急服务设施选址与需求分配”问题已知某区域内有 30 个需求点每个需求点有需求量和平面坐标。现有 10 个备选服务点每个备选点有建设成本和服务容量。需要决定开放哪些备选点并把所有需求点分配给已开放的服务点使总成本建设成本 运输成本最小。这是一个典型的选址分配问题在国赛 B 题中频繁出现具体形式可能是物流中心选址、充电桩布局、消防救援站规划等。它的核心数学模型如下决策变量(x_j)0-1 变量第 (j) 个备选点是否开放(y_{ij})需求点 (i) 分配给备选点 (j) 的需求量连续变量目标函数[ \min \sum_{j1}^m f_j x_j \sum_{i1}^n \sum_{j1}^m c_{ij} y_{ij} ]其中 (c_{ij}) 为需求点 (i) 到候选点 (j) 的单位运输成本这里用欧氏距离表示。约束条件[ \sum_{j1}^m y_{ij} d_i, \quad \forall i ][ \sum_{i1}^n y_{ij} \leq cap_j \cdot x_j, \quad \forall j ][ y_{ij} \geq 0, \quad x_j \in {0, 1} ]第一个约束保证每个需求点的需求被完全满足第二个约束表示只有开放的设施才能提供服务且总分配量不能超过容量。这是一个混合整数线性规划问题。4.2 数据准备与读取实际比赛时数据通常由赛题提供常见格式是 Excel 或 CSV。为了演示完整流程我们先随机生成一份符合场景的数据并将它保存为 CSV 文件模拟赛题数据的读取过程。# 文件路径src/data_preprocess.py import numpy as np import pandas as pd # 设定随机种子保证结果可复现 np.random.seed(42) n_demand 30 # 需求点数量 n_candidate 10 # 候选点数量 # 生成需求点坐标和需求量 demand_points np.random.rand(n_demand, 2) * 100 demand np.random.randint(20, 100, n_demand) # 生成候选点坐标、建设成本、服务容量 candidate_points np.random.rand(n_candidate, 2) * 100 fixed_cost np.random.randint(1000, 3000, n_candidate) capacity np.random.randint(300, 600, n_candidate) # 保存为 CSV模拟外部数据文件 demand_df pd.DataFrame({ id: range(n_demand), x: demand_points[:, 0], y: demand_points[:, 1], demand: demand }) demand_df.to_csv(../data/demand.csv, indexFalse) candidate_df pd.DataFrame({ id: range(n_candidate), x: candidate_points[:, 0], y: candidate_points[:, 1], fixed_cost: fixed_cost, capacity: capacity }) candidate_df.to_csv(../data/candidate.csv, indexFalse) print(数据生成完毕, demand_df.shape, candidate_df.shape)读取数据的函数如下# 文件路径src/data_preprocess.py追加 def load_data(demand_path, candidate_path): demand_df pd.read_csv(demand_path) candidate_df pd.read_csv(candidate_path) demand demand_df[demand].values demand_points demand_df[[x, y]].values fixed_cost candidate_df[fixed_cost].values capacity candidate_df[capacity].values candidate_points candidate_df[[x, y]].values # 计算需求点到候选点的欧氏距离作为单位运输成本 n len(demand_points) m len(candidate_points) cost np.zeros((n, m)) for i in range(n): for j in range(m): cost[i, j] np.linalg.norm(demand_points[i] - candidate_points[j]) return { demand: demand, demand_points: demand_points, fixed_cost: fixed_cost, capacity: capacity, candidate_points: candidate_points, cost: cost }这里把距离直接当作成本单位运输成本 (c_{ij}) 就等于两点之间的欧氏距离。如果你的赛题中运输成本和距离不是线性关系需要根据题目描述调整计算方式。4.3 模型求解代码接下来使用pulp建立模型并求解。代码中变量命名尽量和数学符号保持一致便于对照检查。# 文件路径src/model_solve.py from pulp import LpProblem, LpMinimize, LpVariable, lpSum, LpStatus, value from data_preprocess import load_data def build_and_solve(data): n len(data[demand]) m len(data[fixed_cost]) demand data[demand] fixed_cost data[fixed_cost] capacity data[capacity] cost data[cost] prob LpProblem(Facility_Location, LpMinimize) # 0-1 变量候选点是否开放 x LpVariable.dicts(open, range(m), catBinary) # 连续变量需求点 i 分配给候选点 j 的需求量 y LpVariable.dicts(assign, [(i, j) for i in range(n) for j in range(m)], lowBound0) # 目标函数建设成本 运输成本 prob lpSum(fixed_cost[j] * x[j] for j in range(m)) \ lpSum(cost[i][j] * y[(i, j)] for i in range(n) for j in range(m)) # 约束1每个需求点的需求必须被满足 for i in range(n): prob lpSum(y[(i, j)] for j in range(m)) demand[i] # 约束2只有开放的候选点才能接受分配且不能超过容量 for j in range(m): prob lpSum(y[(i, j)] for i in range(n)) capacity[j] * x[j] # 求解 prob.solve() result { status: LpStatus[prob.status], objective: value(prob.objective), open_facilities: [j for j in range(m) if value(x[j]) 0.5], assignment: { (i, j): value(y[(i, j)]) for i in range(n) for j in range(m) if value(y[(i, j)]) 1e-6 } } return result if __name__ __main__: data load_data(../data/demand.csv, ../data/candidate.csv) result build_and_solve(data) print(求解状态, result[status]) print(最优目标值, round(result[objective], 2)) print(开放的候选点, result[open_facilities])运行这段代码后pulp会调用内置 CBC 求解器进行求解。由于模型规模不大通常几秒内就能得到最优解。求解状态为Optimal说明找到全局最优解。4.4 结果可视化有了求解结果需要把它画出来方便检查方案是否合理也方便后期直接用于论文。# 文件路径src/visualize.py import matplotlib.pyplot as plt import numpy as np from data_preprocess import load_data from model_solve import build_and_solve # 设置中文字体避免中文乱码 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False def plot_solution(data, result, save_pathNone): demand_points data[demand_points] candidate_points data[candidate_points] open_j result[open_facilities] closed_j [j for j in range(len(candidate_points)) if j not in open_j] fig, ax plt.subplots(figsize(10, 8)) ax.scatter(demand_points[:, 0], demand_points[:, 1], s60, cblue, alpha0.7, label需求点) ax.scatter(candidate_points[open_j, 0], candidate_points[open_j, 1], s300, cred, marker^, label开放设施) ax.scatter(candidate_points[closed_j, 0], candidate_points[closed_j, 1], s150, cgray, markerx, label未开放设施) # 画出分配关系线条粗细代表分配量大小 max_amount max(result[assignment].values()) for (i, j), amount in result[assignment].items(): ax.plot([demand_points[i, 0], candidate_points[j, 0]], [demand_points[i, 1], candidate_points[j, 1]], k-, alpha0.3, linewidth0.5 amount / max_amount * 3) ax.set_xlabel(x 坐标) ax.set_ylabel(y 坐标) ax.set_title(应急服务设施选址与需求分配方案) ax.legend() plt.tight_layout() if save_path: plt.savefig(save_path, dpi300) plt.show() if __name__ __main__: data load_data(../data/demand.csv, ../data/candidate.csv) result build_and_solve(data) plot_solution(data, result, save_path../output/solution_plot.png)对模型不熟悉的读者看到图就能直观判断方案是否合理开放的设施是否覆盖了所有需求点、分配连线是否跨度过大、是否存在某个设施被分配了过量需求。这些判断在论文结果分析部分非常有价值。4.5 运行与结果说明在你本机运行完整代码后预期会看到类似下面的输出数据生成完毕 (30, 3) (10, 4) 求解状态 Optimal 最优目标值 35678.45 开放的候选点 [1, 3, 6, 8]不同版本求解器输出的目标值可能在小数位上有差异但整体数量级和开放设施集合应当保持一致。如果开放设施数量明显异常比如只开放了一个设施且分配线密集优先检查容量总和是否过小、固定成本是否过大。示例中固定的随机种子是 42所以你应该看到固定的数据分布。实际比赛中数据是给定的你需要把load_data替换成对赛题数据的读取逻辑。5. 进阶复现技巧敏感性分析与模型评价复现优秀论文时只看一组最优解是不够的。获奖论文通常还会做敏感性分析用来验证模型稳定性和结论可靠性。这一步在国赛评阅时非常加分。5.1 敏感性分析代码最常见的敏感性分析方式是把某个关键参数按比例缩放观察最优目标值和决策变量的变化。以固定建设成本为例# 文件路径src/sensitivity.py from data_preprocess import load_data from model_solve import build_and_solve def sensitivity_analysis(data, cost_ratios(0.8, 0.9, 1.0, 1.1, 1.2)): base_fixed_cost data[fixed_cost].copy() results [] for ratio in cost_ratios: data[fixed_cost] base_fixed_cost * ratio res build_and_solve(data) results.append({ 成本比例: ratio, 最优目标值: round(res[objective], 2), 开放设施数量: len(res[open_facilities]), 开放设施编号: res[open_facilities] }) return results if __name__ __main__: data load_data(../data/demand.csv, ../data/candidate.csv) table sensitivity_analysis(data) for row in table: print(row[成本比例], row[最优目标值], row[开放设施数量], row[开放设施编号])从分析结果中你可以看到当固定成本上升时模型会倾向于少开放设施当固定成本下降时模型可能会开放更多设施以节省运输成本。这个变化趋势就是你的结论可以写成一段文字并配一张表格或曲线图。5.2 将复现结果整理成论文级图表复现的最终目的是提升你的竞赛能力所以图表质量也值得打磨。我的建议是图表一律保存为 PNG 格式分辨率不低于 300 dpi保证论文放大后不模糊。坐标轴要带单位标题要完整图例要清晰。每张图都要对应论文中的一节分析不要出现“图太多但一张都不解释”的问题。表格用三线表风格避免整页表格线。你完全可以把上面绘制的设施选址图、敏感性分析结果直接整理成一篇“复现报告”。这份报告既是你备赛的成果也可以作为团队内部的讨论材料。6. 常见问题与排查思路复现过程中经常遇到各种报错和异常结果。我把常见的几类问题整理成一张排查表建议收藏备用。问题现象常见原因解决思路pulp安装后求解报错找不到求解器安装不完整或 CBC 求解器未正确绑定重新执行pip install pulp运行pulp.pulpTestAll()检查求解状态为Infeasible不可行总容量小于总需求或约束条件互相矛盾检查sum(capacity) sum(demand)必要时放宽容量约束求解速度很慢几十分钟没反应变量数量过大整数变量过多减少候选点数量先用线性松弛验证模型可行性必要时换启发式算法复现结果和论文数据差异很大距离公式不同、单位不同、数据顺序不一致对照论文符号表逐项检查确认欧氏距离还是曼哈顿距离matplotlib 中文标签乱码系统缺少中文字体安装中文字体并设置plt.rcParams[font.sans-serif]目标函数中出现inf或nan数据未清洗存在缺失值或除零问题检查数据是否为空、成本矩阵是否无穷大先做数据检查想用 scipy 跑整数规划但报错scipy 版本低于 1.9不支持整数变量升级 scipy或改用 pulp/ortools这些问题的共性是先检查数据再检查模型最后检查求解器配置。千万不要一上来就怀疑求解器有问题绝大多数情况下问题出在自己写的约束或参数上。7. 最佳实践与工程建议7.1 代码层面保持模块化和可复现强烈建议把数据读取、模型求解、结果可视化、敏感性分析拆成不同文件。这样做有三个好处第一单独调试方便。模型求解报错时不需要去翻可视化代码。第二复用性高。下次遇到选址问题直接改build_and_solve里的参数即可。第三团队协作清晰。三个人分别负责数据、模型、可视化并行效率更高。同时所有随机数生成都要设置固定random_state或seed。数学建模竞赛中结果可复现是基本要求如果你在正式比赛时因为随机种子没固定导致第二次运行结果和第一次不一致会被评委质疑结果可靠性。7.2 论文与代码对照建立问题清单复现每一篇论文前先建立一份“问题清单”大概包括以下问题论文的目标函数具体是什么成本项有哪几类决策变量是连续型还是整数型约束条件是否全部能在代码中体现论文用什么算法求解精确算法还是启发式论文里的关键图表用了哪些数据带着这些问题去复现比漫无目的地读论文效率高得多。复现完成后给这份问题清单补上答案它就变成一篇高质量的学习笔记。7.3 备赛节奏建议如果你距离 2026 国赛还有半年以上时间建议按下面节奏安排第一个月主攻基础建模能力重点复习线性规划、整数规划、统计分析、常见机器学习模型。第二到第三个月每周复现一篇优秀论文先从自己最熟悉的题型开始再逐步扩展。第四到第五个月开始完整模拟比赛流程选 2 到 3 道往年真题按正式比赛的时间限制完成从读题到提交论文的全流程。最后一个月总结之前的复现笔记和模拟经验整理自己的代码模板和写作模板。每次模拟赛和复现完成后都花 30 分钟做一次复盘。复盘内容不用多回答三个问题这次最大的收获是什么最浪费时间的环节是什么下次如何改进8. 总结与下一步学习建议这篇教程用一个设施选址模型串起了优秀论文复现的完整流程从读论文、整理符号表到数据准备、模型求解、可视化再到敏感性分析和问题排查。如果你按照上面的步骤完整跑通一遍你收获的不仅是一套能运行的代码更重要的是理解了一篇获奖论文是如何从题目一步步变成可量化、可验证、可展示的方案的。这种理解靠“看”是得不到的必须靠“做”。接下来如果你想继续深入可以从三个方向扩展第一个方向是算法层面。把pulp求解换成遗传算法、模拟退火或粒子群算法对比精确解与启发式解的差距这个能力在 B 题中经常派上用场。第二个方向是模型层面。尝试把单目标模型扩展为多目标模型例如同时优化成本和响应时间用加权法或帕累托前沿分析处理。第三个方向是写作层面。把你复现的过程整理成一篇完整的小论文包括摘要、问题分析、模型假设、建立模型、求解算法、结果分析、敏感性分析、模型评价哪怕不参加比赛这份写作训练也会让你的建模能力产生质的提升。备赛路上没有捷径但优秀论文复现绝对是一条值得投入时间的路。如果这篇文章对你有帮助建议收藏备用也欢迎在评论区交流你在复现过程中遇到的报错和问题。祝你 2026 国赛顺利拿到理想的成绩。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价