简介:2022年五一数学建模竞赛A题“血管机器人订购与学习”二等奖获奖论文,完整PDF共1个文件,压缩包约703KB。资源基于MATLAB实现,面向数学建模参赛者与运筹优化学习者,适合借鉴动态规划、多周期折扣订购模型、最优订购控制算法及时间序列预测的完整建模思路。正文围绕五个问题层层递进:从第1-8周最低运营成本7625元的基础方案,逐步加入20%/10%设备损毁率、熟练操作手指导数量变化、批量折扣政策,再到第104-112周ARIMA需求预测与应急购买对比,完整呈现获奖队伍的分析路径。已有1255人浏览学习。通过这份论文可系统查看状态转移方程构造、决策变量选取、目标函数设置等关键细节,以及二等奖论文的排版结构、结果表格与对比分析方法,对完成同类订购与库存优化赛题有直接帮助。 2022年五一赛A题“血管机器人订购与学习”,我们队最后拿的是二等奖,全程用MATLAB跑完。比赛结束复盘了很久,这道题给我的感觉特别典型:表面是医疗、机器人、血管这几个听着很高大上的词,剥开之后就是一个带“学习机制”的订购-分配-调度组合优化问题。今年备赛群里已经有人开始问这道题了,我干脆把当时的读题思路、建模逻辑、MATLAB实现过程和写论文的节奏完整整理一遍。这篇文章适合两类人看:一是准备数学建模竞赛、想了解组合优化题怎么下手的同学;二是工作中要处理采购调度、资源分配类问题,想用MATLAB快速建模的工程师。我会尽量把“为什么这么建模”“为什么选这个求解器”讲清楚,而不是只贴一堆代码让你抄。
1. 赛题拆解:血管机器人到底考的是什么
1.1 表面是医疗机器人,本质是组合优化
先还原一下赛题场景。血管机器人可以被理解成一种能进入血管执行检测、清理或药物投放任务的微型机器人,赛题一般会给出若干个任务位点,每个位点在一定时间内有明确需求,比如需要多少台机器人、需要完成多少次任务。我们的决策对象很直白:向供应商订购多少台机器人,初始分配到哪个位点,后续任务过程中机器人如何动态调度。
这三个问题叠在一起,就是典型的组合优化。很多人一开始被“血管”两个字晃花了眼,以为要研究医疗机理、流体力学或者机器人控制,其实比赛不会让你设计真实硬件,建模阶段的核心是把这些决策关系翻译成数学表达式。我当时的第一反应是:这和我之前做过的一个“无人机配送站选址与调度”问题几乎是一个骨架,只是换了业务场景。
如果你能快速把赛题场景“翻译”成自己熟悉的运筹学问题,读题阶段就赢了一半。这道题真正需要建模的层次有三个:
- 订多少:采购数量决策,涉及预算、需求覆盖和成本目标;
- 放哪里:初始分配决策,涉及位点需求量差异和运输/调度成本;
- 怎么学:机器人执行任务后获得经验,能力提升,后续能承接更难的任务或跑更远距离,这个动态因素影响前两个决策。
前两个层次是经典资源分配,第三个层次才是把本题和普通分配题拉开差距的地方。
1.2 “学习”机制是这道题的灵魂
题目里的“学习”不是机器学习、神经网络那种学习,而是指机器人在执行任务过程中积累经验、提升能力,类似游戏角色升级。这个设定才是全题的难点,也是区分度所在。
如果不考虑学习机制,机器人完全同质,问题就退化成静态分配:总需求除以单台机器人的服务能力,加一些约束,直接整数规划就能解。但加入学习之后,机器人从“同质”变成“异质”,能力是时间 t 的函数,状态会随着历史调度路径不断变化。比如初始状态下,一台机器人只能完成简单任务;执行几次任务后,经验值增加,它能处理复杂任务或单位时间服务量变大;再往后可能有能力上限,到达上限后受训收益递减。
这就是典型的动态规划或带状态转移的优化问题。做这道题时,我最重要的一步是想清楚:要把“学习”写成模型内生变量,让它影响目标函数里的效率系数,还是写成外生约束,只做能力阈值判断。我们队最终选择的是把学习效果映射成“效率提升系数”,这样既能保留动态特征,又不至于把模型复杂度推高到三天赛程内解不出来。
1.3 为什么用MATLAB而不是其他工具
参赛工具主流三个:MATLAB、Python、Lingo。我们选MATLAB,有几个很实际的理由。
第一,MATLAB的矩阵运算和编程习惯天生贴合建模。约束条件、决策变量在草稿纸上本来就是向量和矩阵,写进代码几乎不用转换思路。第二,Optimization Toolbox和Global Optimization Toolbox覆盖了赛题可能需要的大部分求解器,intlinprog解决整数线性规划、ga解决遗传算法、fmincon处理非线性约束,够用且稳定。第三,MATLAB画图质量高,比赛最后一天赶论文时,出图快、能直接导出清晰PDF或eps,这个优势会直接体现在评阅观感上。
顺便多说一句,很多新手赛前花大量时间折腾MATLAB下载安装教程,实际上不用。绝大多数高校都有正版校园授权,用学校邮箱直接登录MathWorks账号就能激活,工具箱全开。有那个折腾安装包的时间,不如把Optimization Toolbox里的几个函数文档过一遍,比赛时真能救命。
2. 模型构建:把“订购与学习”翻译成数学语言
2.1 第一层:订购决策的整数规划
先做最基础的订购模型。假设一共有 n 个任务位点,每个位点 i 的需求量为 D_i,一台初始状态的机器人单位时间能完成的服务量为 p0。如果只考虑静态覆盖,理论上需要的机器人总数下限是:
N_min = ceil( sum(D_i) / p0 )
但实际中不能只按下限买,因为位点之间有调度距离、任务有高峰期、机器人还会有检修或损耗,所以要让订购量 N 成为一个决策变量,并假设每个位点分配 n_i 台,满足总和约束 sum(n_i) = N。
目标函数我建议至少包含三大块:采购成本、运行调度成本、未满足需求惩罚成本。写成线性表达式大概是:
min C_purchase * N + sum_i sum_j c_ij * x_ij + M * sum_i max(0, D_i - p0 * n_i)
其中 x_ij 表示从位点 i 调度到 j 的机器人数量,c_ij 是单位调度成本,M 是惩罚系数。注意惩罚项里的 max(0, ...) 不是线性的,写进整数规划时要引入辅助变量 y_i,y_i >= D_i - p0 * n_i,y_i >= 0,然后目标函数里加 M * sum(y_i)。这个线性化技巧比赛里非常常用,评阅老师看到规范线性化处理会认为你基本功扎实。
这段模型用MATLAB的话,直接调 intlinprog 就能解。决策变量就是 [N; n_1; ...; n_n; x_11; ...; x_nn; y_1; ...; y_n],intcon 指定所有变量都是整数。
2.2 第二层:学习曲线的量化和状态转移
为了让模型贴合“学习”设定,需要给每个机器人引入一个经验值 e,初始为0,每完成一次任务增加固定经验,或者根据任务难度加权增加。能力函数我建议先用经典学习曲线形式,比如:
p(e) = p_max - (p_max - p0) * exp(-lambda * e)
这个形式的含义很直观:初始能力是 p0,经验增加后能力逐渐逼近上限 p_max,lambda 控制学习速度。相比阶梯式升级,连续函数写论文更好看,也更方便做灵敏度分析。lambda 大,说明机器人学得快;lambda 小,说明训练周期长。
真正让模型复杂起来的是状态转移:机器人完成位点 i 的任务后,经验增加,能力提升,那么它在下一个时段被分配到其他位点时,能贡献的服务量发生了变化。这意味着按时间段 T 拆分决策变量,让 n_i(t) 表示 t 时段位点 i 可用的机器人数量,x_ij(t) 表示 t 时段从 i 调往 j 的机器人数量,而机器人能力是经验的函数。这个模型已经从静态分配变成了多阶段动态优化,不能再用一个线性规划直接收工。
这里有一个建模取舍:如果赛题给的时间周期很长、位点很多,把所有时间片的变量铺开会导致整数变量爆炸。我们当时的处理是分两层:外层用离散事件模拟的思路,把时间轴分成若干个阶段,每个阶段内假设机器人能力不变;内层在每个阶段用整数规划求解最优分配。这种“时分段+内层优化”的结构,好写、好解释、也容易检查错误。
2.3 求解策略:精确解怎么用,启发式什么时候上
建模完成之后,求解器的选择取决于问题规模。如果你发现决策变量只有几十到几百个,直接用 intlinprog 求全局最优,这是精确解,最稳妥。我们当时第一问就是这样,几十个变量,几秒钟出结果,结果可以直接写进论文。
但到了后面几问,加入学习机制、时间片、动态调度约束后,整数变量可能膨胀到几千甚至上万。这时候 intlinprog 极可能长时间跑不出最优解,甚至在比赛剩余时间内连可行解都给不出来。我的经验是:在大规模情况下,不要死等精确解,尽快切换到启发式算法。
遗传算法是这一类问题最省事的启发式选择。用MATLAB的话,ga 函数天然支持整数变量、线性约束和非线性约束,核心是把每个个体编码成一组决策变量值,适应度函数返回目标函数值。你需要调的核心参数就三个:种群规模 PopulationSize、最大迭代代数 MaxGenerations、变异概率 MutationFcn。种群我一般开 200 到 400,代数 500 左右,跑一轮大概几分钟到十几分钟,比赛节奏完全可以接受。
还有一个小技巧:用启发式算法之前,先跑一遍 intlinprog 的宽松版本(比如把整数约束去掉,解实数松弛解),把松弛解作为 ga 初始种群的一个个体塞进去,能明显加快收敛。这个操作在论文里可以写成“基于松弛解构造初始种群”,是一个加分的细节。
3. MATLAB实现的关键细节与调试过程
3.1 代码组织:比赛三天不崩盘的工程习惯
数学建模比赛的三天里,最大的敌人不是模型复杂度,而是代码越写越乱,最后自己和队友都看不懂。第一天晚上写了一半的代码,第二天早上接着改的时候,如果变量名全是 a、b、c,函数文件四处乱放,心态很容易崩。
我建议按工程化方式组织:
project/ ├── data/ % 原始数据,统一放csv或mat文件 ├── model/ % 各小问对应的模型函数 ├── solver/ % 求解器封装函数 ├── plot/ % 画图脚本 ├── main_q1.m % 第一问主脚本 ├── main_q2.m % 第二问主脚本 └── README.md % 记录团队分工和进度每个主脚本只做四件事:加载数据、设定参数、调用模型函数、保存并绘制结果。模型函数只做一件事:定义决策变量、写目标函数、写约束,并调用求解器。这样分工明确之后,三个人各改各的部分也不会互相踩代码。
另一个细节是:所有中间结果跑完立刻存 mat 文件。因为后面写论文时,可能需要回到某个图、某个数据值,从头跑一次几十分钟的求解器太浪费时间。我当时习惯在每段代码结尾加一句:
save(fullfile('data', sprintf('result_q1_%s.mat', datestr(now, 'mmdd_HHMM'))), 'N_opt', 'cost_total', 'assign');这个习惯在比赛第三天救了我一次,因为模型参数改了一个之后输出结果变化很大,要对比新旧两版数据,如果没有存档就只能翻聊天记录。
3.2 求解器调用与参数调优实录
先看 intlinprog 的标准调用。用上面的订购模型举例,决策变量是 x,长度取决于变量数,整数变量索引放 intcon,线性不等式约束为 A*x <= b:
f = [...]; % 目标函数系数向量 intcon = 1:n_vars; % 全部整数变量 A = [...]; b = [...]; % 不等式约束 Aeq = [...]; beq = [...]; % 等式约束 lb = zeros(n_vars, 1); % 下界 ub = [...]; % 上界,比如订购量上限 options = optimoptions('intlinprog', 'Display', 'iter', 'MaxTime', 300); [x_opt, fval, exitflag] = intlinprog(f, intcon, A, b, Aeq, beq, lb, ub, options);注意 MaxTime 这个参数,我习惯限制在 300 秒左右。比赛时不可能无限等下去,超过时间没解完就记录下来,分析是不是约束写错或者需要换启发式。exitflag 必须检查,不是 1 的结果都要重新审视模型。
用 ga 时,关键的 options 配置:
options = optimoptions('ga', ... 'PopulationSize', 300, ... 'MaxGenerations', 600, ... 'Display', 'iter', ... 'UseParallel', true, ... 'ConstraintTolerance', 1e-6); [x_opt, fval, exitflag] = ga(@fitness_fun, n_vars, A, b, Aeq, beq, lb, ub, @nonlcon, options);UseParallel 开成 true,可以多核并行计算适应度,代数和种群规模较大时能省一半时间。还有一个小坑:ga 是随机算法,如果不设置随机数种子,每次跑出来的结果不一样。比赛现场一定要在脚本开头写 rng(2022) 这类固定种子,保证结果可复现,这也是写论文时要说明的“实验设置”。
3.3 从数据到图表:论文图的产出流程
数学建模论文的评阅速度很快,图表是第一印象。MATLAB画图功能很强,但很多同学产出质量差,主要问题是字号小、线条细、标签不清晰。我们当时的统一规范是:图内字号至少 10,坐标轴带单位,线宽至少 1.5,图例放在内部空白处而不是外侧,所有图片统一用 exportgraphics 导出。
figure('Position', [100, 100, 800, 500]); plot(1:num_periods, N_opt_by_period, 'o-', 'LineWidth', 1.5); xlabel('时间周期'); ylabel('最优订购量'); title('各阶段最优订购量变化'); grid on; exportgraphics(gcf, 'img/q1_order_trend.png', 'Resolution', 300);另外,如果论文要求矢量图,用:
print(gcf, 'img/q1_order_trend', '-depsc2');导出 eps 格式,放进 LaTeX 模板印刷效果最好。如果要求不高,直接 300 dpi 的 png 也够用。
4. 论文写作与获奖经验:二等奖是怎么炼成的
4.1 摘要:决定命运的三小时
数学建模论文里,摘要是评阅老师大概率最先看、也最认真看的部分。到第三天上午,我几乎只做一件事:打磨摘要。很多队伍的错误是比赛最后两小时才开始写摘要,然后草草收尾,这是最可惜的丢分方式。
摘要不需要面面俱到,但要包含四要素:针对什么问题、用了什么模型、采用什么算法、得到什么关键结果。关键结果必须带数据,而不是“结果较好”“效果明显”这种空话。比如“在预算约束下,最优订购量为42台,总成本较随机分配方案降低18.6%”,这句话的信息量远超十句模糊描述。
我当时写摘要的习惯是先写完一个通稿,然后反复删改,把每个问题对应的模型名和核心结论单独成句。各问题之间的衔接不用“首先、其次、再次”这种写作文式的空词,直接给出模型名加结果,干净利落。
4.2 灵敏度分析:把模型的“稳”写出来
评阅老师很看重模型是否稳健,而灵敏度分析是证明稳健性的最直接方式。这套题至少有三个关键参数值得做扰动分析:需求波动、学习速度参数 lambda、预算上限。
我们当时的做法是:以基准参数下的最优解为中心,把每个参数分别往上下各调整 5%、10%、15%,重新跑一遍模型,记录目标函数值和最优解变化。比如需求波动对订购量的影响,可以画一张曲线图或用表格列出结果。重点不是把每一组结果都放上去,而是说明三个问题:最优解对哪些参数敏感、在什么范围内结果稳定、哪些参数变化会导致模型出现本质性变化。
这样做的好处是,即使你的模型不是满分模型,评阅老师看到你认真做了灵敏度分析,也会认为你对问题理解深入。竞赛论文不是要证明模型百分之百正确,而是要证明你的分析过程完整、谨慎。
4.3 七十二小时分工:建模、编程、写作一个都不能拖
三天赛程,我建议第一天就把“建模、编程、写作”三个人角色定死。不是说一个人只干一件事,而是每个人有一个主战场,避免第三天全员挤在一起抢电脑。
我们队的节奏供参考:
- 第一天上午:三个人一起读题、讨论假设、定模型框架。下午建模同学开始写数学公式,编程同学先写数据读取和基础模型代码,写作同学开始起草问题重述与模型假设。
- 第二天:编程同学主导所有求解器调用,建模同学负责调参和补约束,写作同学开始写模型建立与求解章节,同时收集好所有结果数据。
- 第三天上午:三人合体,重点修订摘要、画图、做灵敏度分析。下午统一格式,检查全文变量符号是否一致,最后留出至少一小时全文通读。
最容易失控的是第二天,求解器不收敛、代码报错、模型约束缺条件,好几个问题同时爆发。这个时候我建议队长尽量做“定海神针”,规定每次只处理一个问题,处理完记录到README里,不然后面改着改着就忘了为什么改。
5. 常见错误与排查技巧实录
5.1 求解器不收敛与无解排查
这应该是比赛里最崩溃的瞬间。intlinprog 返回 exitflag 为负值,或者 ga 跑到最大代数还没有收敛,先不要慌,按照顺序排查:
- 检查约束是否矛盾。比如既要求总订购量>=需求总量,又设了很小的订购量上界,无解是正常的。把 ub、A、b 拉出来逐行核对。
- 检查决策变量上下界是否合理。零向量下界是标配,上界不要拍脑袋设置,最好从数据里估算。
- 检查矩阵维度是否匹配。MATLAB 报错常见于 A*x 维度对不上,用 size(A)、size(x) 快速确认。
- 检查参数单位是否统一。题目里时间可能是小时,成本可能是千元,单位不统一会导致惩罚项权重失真,模型跑出的解即使可行也没有意义。
如果这些都查过还是不行,先解一个简化版:固定一部分决策变量,只优化剩下的变量,确认子问题可解,再逐步放开。这种二分式排查,比无头苍蝇式乱试高效得多。
5.2 数据与矩阵常见坑
数据处理有一个特别容易踩的坑:数据文件里有缺失值或无效值,但直接参与运算后,结果莫名偏差。MATLAB的 load 读入矩阵后,一定要用 any(isnan(data)) 和 any(isinf(data)) 检查一遍,发现异常立即处理,而不是等求解器报错才回头找。
另一个很坑的地方是整数变量和连续变量混在一起时,目标函数系数数量级差距过大。比如采购成本是几千上万的量级,惩罚系数 M 为了强制覆盖约束设成1e6,这时 intlinprog 的数值稳定性会变差。解决办法是尽量把目标函数各项做归一化,或者把惩罚系数设置成采购成本量级的1.5倍左右,不要无脑加巨大系数。
画图时还有一个低级的坑:plot 多个曲线时,图例顺序和绘图顺序不一致。第三天写论文时越慌乱越容易犯,我后来都是把图例位置放到坐标区外部,或者干脆在标题里把曲线含义写清楚。
5.3 论文排版的最后一道关卡
最后半天,论文排版往往是决定二等奖和三等奖的隐形分界线。变量符号前后不一致是最常见的问题。建模时用 n_i 表示位点分配量,写论文时改成 x_i,代码里又写 q_i,评阅老师读起来会很痛苦。我们队的速度是:第三天上午专门花二十分钟把全文所有变量列成一个符号表,统一替换代码和论文中的命名。
图表编号、公式编号也容易乱。建议公式用 LaTeX 模板自带的自动编号,图题、表题用“图1 需求分布拟合结果”这样带冒号的完整句式,不要只有图没有题。另外,参考文献不要凑数,列出来的每篇要真在正文中引用到。
最后再分享一个经验:比赛结果出来后我们复盘,发现二等奖和一等奖之间,很多时候不是算法差得多远,而是摘要的精炼度、图表的美观度、灵敏度分析的完整性有差距。这题做下来,我对“学习机制如何影响调度决策”这件事有了更具体的体会,这种动态优化思维,后来看很多行业里的排产、库存、路径规划问题,本质上都是同一个骨架。希望这篇复盘能帮你们在下次比赛里少踩几个我们踩过的坑。
本文还有配套的精品资源,点击获取