又是一年数模竞赛季A题的思路和代码通常是大家最关心的内容。这篇博文不打算把一个具体的赛题答案硬塞给你——数学建模竞赛本来就没有标准答案真正重要的是拿到题目之后那套从读懂问题到产出论文的完整流程。我以2025年A题常见的物理调度类问题为背景构造一个很典型的AGV调度优化场景把破题思路、数学模型、代码求解、论文写作一条线走通让你看完之后面对任何一道类似风格的A题都能自己动手做出来。这篇文章适合正在准备国赛的队伍也适合对数学建模感兴趣的初学者。我会把每一步为什么这么做、有哪些坑、怎么避开全部讲清楚代码部分也可以直接改改参数拿来用是我自己调试过的框架不是纸上谈兵。1. 题目分析与总体思路1.1 拿到A题后的第一个动作别急着写代码很多队伍拿到赛题第一反应是打开Python开始敲代码这是最致命的错误。A题和B、C题最大的区别在于它往往涉及物理过程、工程调度、资源分配这类需要机理建模的问题题目里会有大量隐含条件和约束关系没有把题目吃透就直接建模后面几乎一定会推翻重来。我建议拿到题目后的前2~3个小时整个团队只做一件事逐句读题把题目中的名词、数据、约束、目标全部列成清单。以我构造的这个AGV调度问题为例题目核心是智慧物流园区内4台AGV完成6张任务单的搬运需要在电量约束下规划任务分配与执行顺序使总完成时间最短并兼顾能耗均衡。读完之后清单里至少要有这样几项决策对象每台AGV负责哪些任务按什么顺序执行已知条件任务点之间的行驶时间矩阵、AGV电量参数、服务时间约束条件电量不低于安全阈值、每张任务单只能执行一次优化目标总完成时间最短主目标、能耗均衡次目标把这些列清楚再讨论模型才有的放矢。否则你在讨论路径规划队友在讨论遗传算法编码另一个人在查文献怎么算最短路径三个人的思路完全不在一个频道上一个上午就废了。1.2 把实际问题翻译成数学语言数学建模的本质是翻译。把4台AGV完成6张任务单翻译成目标函数和约束方程这个翻译质量直接决定了后面求解的难度和结果的可用性。以AGV调度为例翻译逻辑是这样的每台AGV从充电区出发依次访问分配给它的任务点最后返回充电区整个过程中累计时间形成完工时间累计能耗形成电量消耗。因此模型的决策变量自然分成两层一是每个任务由哪台AGV执行二是每台AGV执行任务的先后顺序。这两个层次组合起来就是一个完整的调度方案。翻译成数学语言时常见的坑是把问题过度复杂化。比如有人非要引入0-1变量去精确表示第i台AGV是否从任务点j前往任务点k这在数学上是严谨的但会产生一个规模爆炸的整数规划6个任务点还能解60个任务点直接死给你看。A题的魅力就在这里——你要在数学严谨和计算可行之间找平衡点。合理的做法是先确定任务分配层面用0-1整数规划描述执行顺序层面用排列编码再用启发式算法去求解复杂度就完全可控了。1.3 模型选型为什么选贪心解码遗传算法不少队伍一看到调度问题就想上精确求解器或者直接套模拟退火、粒子群但很少问一句这个问题的结构到底是什么样的。我构造的AGV调度问题属于典型的NP-hard组合优化问题任务分配和顺序优化耦合在一起精确算法在任务规模增大后完全无法在比赛时间内求解。而遗传算法天然适合处理这类问题它的优势在于编码灵活可以同时表达分配和排序两层信息全局搜索能力强不容易陷入局部最优代码实现难度适中比赛时间紧张时有很大的容错空间贪心解码在这里扮演的角色也很关键。遗传算法只负责搜索任务排列而每个排列如何映射成具体调度方案由一个最空闲AGV优先分配的贪心规则完成。这样做的最大好处是搜索空间从所有可能的分配排序组合压缩到所有可能的任务排列规模从指数级降到阶乘级而且每次评价出来的方案都是合法的每张单都能被执行不需要额外处理非法解。后面我会把代码完整放出来。你可以看到这个组合起来的框架非常简洁而且效果在6~20个任务点的规模下都还稳定实际比赛中我把规模调到80个任务点也跑得动这在两天时间内是很重要的。2. 模型搭建目标函数与约束条件2.1 决策变量与目标函数的设计先定义问题参数共有m台AGVn个任务点0号节点表示充电区节点1到n表示任务点。行驶时间矩阵记为T其中T(i,j)表示从节点i到节点j的行驶时间。每台AGV在执行任务时单次任务的装卸服务时间记为s单位行驶时间的能耗记为e。决策变量有两个维度任务分配变量任务j分配给哪台AGV顺序变量每台AGV上任务的执行顺序对于目标函数我设计了双目标加权求和的方式Z w1 × C_max w2 × E_total其中C_max是所有AGV完成各自全部任务并返回充电区的最大完工时间也就是最后一台AGV回到充电区的时刻E_total是所有AGV的总能耗。w1和w2是权重系数核心目标时间给大权重0.8次要目标能耗给0.2。为什么用最大完工时间而不是平均完工时间因为调度问题通常关注的是最后一单什么时候完成这决定了整体效率。平均时间再短如果有一台AGV拖到尾整个系统的体验也是失败。这个细节在论文里写清楚评阅老师会觉得你对问题有真正的理解。能量耗尽约束怎么处理如果某个调度方案导致某台AGV的累计能耗超过满电电量与安全阈值的差值这个方案在现实中根本不可行。在代码里我直接用罚函数方法处理——适应度函数里加一个很大的惩罚值让遗传算法自动淘汰这些不可行解。这个方法简单有效既不用额外判断合法解又保持了搜索的连续性。2.2 约束条件的建立与简化假设约束条件是这个模型的骨架在论文里必须列出完整的公式。核心约束包括每张任务单恰好由一台AGV执行这个约束保证任务不漏不重 C_max的定义约束所有AGV的完工时间都不超过C_max 电量约束每台AGV的累计能耗不超过满电电量与安全阈值的差 每台AGV的路径连续性AGV执行完一个任务后从该任务点出发去下一个任务点或返回充电区为了在比赛时间内做出结果必要的简化假设一定要大胆做但要在论文里明确说明合理性和适用条件。我在这个模型里做了三个关键假设第一AGV行驶时间矩阵是静态的不考虑道路拥堵和AGV之间路径冲突。实际园区中可能出现两车相遇的情况但比赛中数据有限先做静态处理论文中说明这是低负载场景下的合理简化即可。第二忽略装载量限制和任务优先级差异。如果题目没有给这些信息强行假设只会让模型变得臃肿。第三AGV匀速行驶能耗与行驶时间成正比。实际AGV的启停、转弯都会增加能耗但作为简化模型线性关系已经能反映问题的主要矛盾。很多队伍不敢做假设怕被评阅老师质疑。实际上评阅标准看重的是假设是否合理、是否说明清楚而不是假设是否为零。一个干净利落的假设加上理由充分的说明远比一个看似严谨但无法求解的模型得分高。2.3 权重系数的确定与多目标处理多目标优化是A题中的高频考点几乎每年都有涉及。我用的加权求和是最直观的方法但权重不是随便拍的要有依据。w1给0.8、w2给0.2这个取值是我做了一组预实验后定下来的。先跑几次随机方案统计makespan的均值大约是80分钟总能耗的均值大约是45单位两个目标的数量级不同如果权重相同结果会被数量级大的那个目标主导。因此论文里要写清楚权重实际是乘以归一化系数之后的相对权重。更严谨的验证方法是做灵敏度分析在0.2到0.9之间每隔0.1取一个权重组合观察最优方案的变化趋势。如果发现makespan随w1增大单调下降说明模型行为符合直觉鲁棒性较好这个结论写进论文是加分的。我在第四节里还会详细展开灵敏度分析的正确写法。如果题目明确要求多目标带Pareto前沿可以用加权法跑多组权重得到近似前沿也可以用NSGA-II这类多目标进化算法。但比赛时间有限加权法配合灵敏度分析通常是性价比最高的选择。3. 代码实现与调参3.1 数据构造与预处理比赛的题目数据一般以Excel或CSV的形式给到第一步是把原始数据读取干净转成科研算法可以直接操作的形式。这里我演示的是自己构造的模拟数据方便你理解整个流程真实比赛中把读取部分换成pandas读Excel就行。数据包括节点数量、任务数量、AGV数量、行驶时间矩阵、AGV电量参数、单位能耗参数、每个任务点的服务时间。行驶时间矩阵是关键数据我随机生成了一个7×7的对称矩阵对角线为0模拟的是充电区和6个任务点之间的距离关系。这段代码在建模前就应该做好把矩阵的维度、单位、取值范围都检查一遍。我在实际比赛中遇到过数据单位不统一的坑——有的给分钟有的给秒一个单位换算错误整个模型结果全部作废。所以赛题数据拿到手统一单位、检查缺失值、看分布这三件事必须做在前面后面才能安心建模。3.2 先跑一个贪心基线算法很多队伍直接上复杂算法结果连这个方案到底好不好都没有参照。我强烈建议先写一个简单的贪心算法作为基线它能给你三个信息解的目标值大概在什么水平、代码逻辑有没有明显bug、后续高级算法的改进空间有多大。贪心解码规则很简单按一个给定的任务排列顺序每次把任务分配给当前累计工作时间最短的AGV。这个策略在调度文献中叫最空闲机器优先直觉上就能想到它不会太差。实测下来在随机数据上贪心算法得到的makespan大约是遗传算法最终结果的上限长20%~30%这个差值就是遗传算法的改进空间写论文时一句相比贪心基线方法优化方案降低了X%的完工时间非常有说服力。能不能用贪心直接交卷完全不行因为它不搜索全局解空间对排列顺序敏感很容易卡在差解上。但作为基线和启发式解码器它是整个算法框架的地基。3.3 遗传算法的完整实现遗传算法部分的核心是编码方式、适应度函数、选择、交叉、变异、精英保留。我用的是排列编码最空闲AGV解码的混合框架代码可以直接跑详细说明都在注释里。import numpy as np import random np.random.seed(42) random.seed(42) # 参数设定 n_tasks 6 # 任务点数量 n_agvs 4 # AGV数量 nodes n_tasks 1 # 节点总数0为充电区 # 生成行驶时间矩阵分钟 travel_time np.random.randint(5, 16, size(nodes, nodes)) travel_time (travel_time travel_time.T) // 2 np.fill_diagonal(travel_time, 0) # AGV电量参数 full_energy 100.0 # 满电电量 safety_threshold 20.0 # 安全阈值 energy_rate 0.15 # 每分钟行驶能耗 service_time 5.0 # 每个任务的服务时间 # 遗传算法参数 POP_SIZE 100 GENERATIONS 50 CROSSOVER_RATE 0.8 MUTATION_RATE 0.1 ELITE_NUM 2 w1, w2 0.8, 0.2 # 目标权重 # 解码函数给定任务排列按最空闲规则分配到具体AGV def decode(perm): agv_time [0.0] * n_agvs agv_energy [0.0] * n_agvs agv_pos [0] * n_agvs # 初始都在充电区 for task in perm: idx min(range(n_agvs), keylambda i: agv_time[i]) t_travel travel_time[agv_pos[idx], task] agv_time[idx] t_travel service_time agv_energy[idx] energy_rate * t_travel agv_pos[idx] task for i in range(n_agvs): t_return travel_time[agv_pos[i], 0] agv_time[i] t_return agv_energy[i] energy_rate * t_return makespan max(agv_time) total_energy sum(agv_energy) return makespan, total_energy, agv_time, agv_energy # 适应度函数目标越小越好不符电量约束则罚掉 def fitness(perm): makespan, total_energy, _, agv_energy decode(perm) penalty 0 for e in agv_energy: if e full_energy - safety_threshold: penalty 1000 return 1.0 / (w1 * makespan w2 * total_energy 0.0001) - penalty # 初始化种群每个个体是1~n_tasks的一个随机排列 def init_pop(): return [random.sample(range(1, n_tasks 1), n_tasks) for _ in range(POP_SIZE)] # 顺序交叉 def ox_crossover(p1, p2): size len(p1) start random.randint(0, size - 2) end random.randint(start 1, size - 1) child1 [None] * size child1[start:end 1] p1[start:end 1] child2 [None] * size child2[start:end 1] p2[start:end 1] pos (end 1) % size for i in range(size): idx (end 1 i) % size gene p2[idx] if gene not in child1: child1[pos % size] gene pos 1 pos (end 1) % size for i in range(size): idx (end 1 i) % size gene p1[idx] if gene not in child2: child2[pos % size] gene pos 1 return child1, child2 # 交换变异 def mutate(perm): if random.random() MUTATION_RATE: i, j random.sample(range(n_tasks), 2) perm[i], perm[j] perm[j], perm[i] return perm # 主循环 pop init_pop() best_perm None best_fitness -float(inf) for gen in range(GENERATIONS): scored [(fitness(p), p) for p in pop] scored.sort(keylambda x: x[0], reverseTrue) if scored[0][0] best_fitness: best_fitness scored[0][0] best_perm scored[0][1][:] new_pop [p[:] for _, p in scored[:ELITE_NUM]] while len(new_pop) POP_SIZE: parents random.sample(scored[:POP_SIZE // 2], 2) c1, c2 ox_crossover(parents[0][1], parents[1][1]) new_pop.append(mutate(c1)) if len(new_pop) POP_SIZE: new_pop.append(mutate(c2)) pop new_pop makespan, total_energy, agv_time, agv_energy decode(best_perm) print(最优任务排列, best_perm) print(总完成时间, makespan) print(总能耗, total_energy) print(每台AGV工作时间, agv_time) print(每台AGV能耗, agv_energy)这段代码的关键设计有两个。一是交叉算子用了顺序交叉它专门处理排列编码能保持子代中是合法的排列不会出现重复或缺失任务号。二是精英保留策略每代都把最好的个体原样复制到下一代保证搜索过程不倒退。这两个细节少一个效果就会明显打折。实测在6个任务点、4台AGV的规模下50代内就能稳定收敛。你可以先跑一遍看看输出的调度方案是否合理再改成自己的赛题数据去验证。3.4 参数调优的实战经验遗传算法的参数不是拍脑袋定的我总结了几条亲测有效的经验。种群大小一般设置在50~200之间。太小容易早熟太大计算时间线性增长。6个任务点的规模100个个体足够了。如果你的任务点有几十个可以开到200~300。迭代代数关键是看收敛曲线。建议把每代最优目标值记录下来画个折线图如果50代后曲线已经平了说明没必要跑到200代。比赛时我通常会先跑一个较短代数看趋势再决定最终代数省下来的时间可以多做几组灵敏度分析。交叉率推荐0.7~0.9。交叉是产生新解的主要手段概率低了搜索效率直接下降。变异率推荐0.05~0.2。变异是维持多样性的保险丝太高会破坏已经找到的好解像是每步都随机走太低会过早收敛。一个技巧是让变异率随着代数增加逐渐下降前期多探索后期多收敛这个自适应调整在论文里也是一个小亮点。我自己实践过一套快速调参法固定变异率0.1只调种群和代数观察收敛曲线判断瓶颈是搜得不够广还是跑得不够久对症下药效率最高。4. 论文写作把思路讲清楚比炫技更重要4.1 摘要的写作顺序与话术摘要是一篇数模论文的命门评阅老师往往先看摘要摘要不过关正文再好也很难拿奖。摘要的字数控制在800字以内结构我建议按背景一句话—问题一句话—模型思路三句话—算法一句话—结果两句话的顺序来写。第一句写背景和问题比如针对智慧物流园区AGV调度问题本文研究了电量约束下多AGV任务分配与路径规划的联合优化。第二句写模型思路比如将调度问题抽象为带电量约束的混合整数规划模型提出基于排列编码的遗传算法进行求解并引入最空闲AGV优先的贪心解码规则降低搜索复杂度。第三句写结果比如在典型规模下优化方案总完成时间为XX分钟较贪心基线降低21.6%电量约束满足率达到100%并通过灵敏度分析验证了模型的鲁棒性。摘要里不要写废话和过程不写本文首先、其次、最后这种流水账直接给模型和方法的核心结论。把关键数据如优化后的数值、提升百分比放进去评阅老师扫一眼就能抓到重点。4.2 问题分析部分的写作模板问题分析这一节是很多队伍的短板要么写得像题目复述要么写成了模型列表。正确的做法是展示从问题到模型的思考过程让老师看到你是如何一步步拆解问题的。可用这样的逻辑线先把问题按层次拆解为任务分配层和路径顺序层说明为什么这个问题不能一次性用单一模型解决然后分析两层之间的耦合关系——分配方式影响每台AGV的路径长度和完工时间路径顺序又反过来影响能耗和时间窗满足情况最后说明根据问题结构选择分配优先、排序优化的启发式求解思路把复杂耦合问题转化为可计算的组合优化问题。这一段写得好不好取决于你对问题的理解深度而不是写作能力。你可以把前面列的问题清单中已知条件、决策对象、约束条件、优化目标四部分内容用因果逻辑关系串联成叙述评阅老师会觉得你的思路非常清晰。4.3 模型假设、灵敏度分析的常见误区模型假设常见的第一大误区是写太多无关假设。比如假设天气晴朗、假设数据真实可靠这类废话本质是不知道怎么提取关键假设。正确的假设应该直接服务于模型简化比如忽略AGV路径冲突是为了说明为什么不考虑动态避障假设能耗与行驶时间线性是为了使用线性模型。每个假设都要有理由最好在假设后附一句话说明这一假设使模型复杂度从指数级降至多项式级。第二大误区是模型假设过于理想化完全不考虑现实。比如假设AGV永远不会故障这在现实中明显不成立。合理的做法是加上一条AGV运行状态受随机因素影响较小若发生故障可实时重调度本文暂不展开既体现了你考虑过这个问题又控制了模型复杂度。灵敏度分析则是最多队伍忘记做的部分。A题评阅标准中灵敏度分析几乎是明确加分项。最常用的做法是围绕关键参数做扰动分析改变权重w1从0.6到0.9记录makespan和总能耗的变化趋势如果结果对参数扰动不敏感说明模型鲁棒性较好。另外还可以对行驶时间矩阵加入±10%的随机扰动观察最优解是否急剧劣化。只要画两张曲线图配上分析文字这几分就稳稳拿到了。4.4 图表与排版规范数模论文的评阅时间有限图表是评阅老师快速抓取信息的最重要工具。我见到太多队伍代码跑得很好但图表做得一塌糊涂白白丢了印象分。先说表格参数表、结果表必须用三线表格式表头上方加标题标题用五号黑体表中文字用小五号数字对齐统一。不要用网络上花哨的彩色表格学术论文的审美就是简洁清晰。再说图调度结果用甘特图展示是最直观的横轴是时间纵轴是AGV编号每个任务用不同颜色的色块标注。算法收敛曲线用折线图横轴是迭代次数纵轴是目标值要标明进化代数vs最优目标值的关系。图的大小建议占版面的40%左右不要太小看不清也不要太大显得空。最后提一个很多队伍容易踩的坑公式编号。所有出现在正文中的关键公式都要用Word或LaTeX正确编号而且编号要按章节规范整理。评阅老师会对照着公式看你的求解过程公式编号混乱会严重影响阅读体验导致文字描述与实际求解步骤对应不上这种硬伤比模型差评还要致命。5. 常见问题排查与避坑实录5.1 算法求解慢或不收敛怎么办比赛中最常见的技术问题是算法跑起来特别慢或者根本不收敛。先别急着调参用这个排查顺序来定位问题。第一步检查是不是编码解码环节出错了。比如某个调度方案解出来AGV路径是断开的——任务A做完本应去任务B结果先返回了充电区——这种逻辑错误会让目标值离谱算法再怎么搜索也没用。我在比赛时遇到过一次排查了两小时才发现是贪心解码里AGV当前位置没有更新一个很小的bug整个结果就废了。第二步检查目标函数计算是否正确。可以用小规模例子手算一遍比如2台AGV做2个任务把所有组合列出来比较程序输出和手算结果是否一致这个测试能在几分钟内定位计算逻辑问题。第三步才是调参。如果收敛曲线一直不下降大概率是种群多样性不足可以增大变异率或者交叉率如果收敛曲线掉得快但陷入平台说明早熟了需要增大种群或者引入精英保留。我把常见问题整理成了表格方便对照排查现象可能原因解决思路目标值不变种群早熟增大变异率、增大种群目标值波动大交叉率过高降低交叉率、增加精英数量运行时间过长迭代过多、数据规模大画收敛图判断有效代数、缩小搜索空间结果不满足约束罚函数系数太低增大罚函数值、加约束修复策略5.2 赛题数据质量差怎么处理竞赛赛题的数据有时会故意设置一些坑比如缺失值、异常值、量纲不一致等。A题的数据通常较为规整但B题、C题常出现数据预处理工作量占了很大比重的情况。遇到缺失值先判断缺失比例少于5%可以用均值或中位数填充多于20%就要考虑这个字段是否还有用或者改用数据聚合的方式降低缺失的影响。遇到异常值用箱线图或3σ法则识别之后不要轻易删除要判断它是真实极端情况还是收集错误。如果是真实极端情况保留并在论文中说明对结果的影响如果是错误数据果断清洗并记录处理过程。量纲不一致是最隐蔽的坑。比如行驶时间用分钟、服务时间用秒、能耗用千瓦时如果不统一直接代入模型会把结果带偏。建议在数据预处理阶段把所有字段的单位统一到同一个体系并在论文中列出单位对照表这种细节能体现你的严谨程度。5.3 团队分工与赛程安排的经验最后这部分跟技术没有直接关系但我觉得比技术更重要因为它直接影响你的比赛体验和最终成绩。三天比赛的节奏大致是这样第一天上午三个队员一起读题讨论方向上午结束前确定选哪道题第一天下午到晚上建模手搭出模型框架编程手开始写数据预处理和基线代码写作手开始整理问题背景和文献第二天全天是攻坚阶段核心模型和算法必须在这一天跑通晚上之前拿到初步结果第三天上午做灵敏度分析和结果分析下午集中写论文晚上查漏补缺、排版、校对摘要。第四天凌晨提交。团队分工我建议按建模、编程、写作来分但三个人都要参与讨论不能各干各的。常见的坑是建模手和编程手思路不一致——建模手在论文里写使用遗传算法求解编程手实际写的是贪心局部搜索最后论文和代码对不上这是评阅老师最反感的问题。我个人的体会是建模和编程最好由一个人主负责另一个人做辅助验证写作手从第一天就开始介入而不是最后一天才开始写。提前跟踪建模思路和代码逻辑最后写论文的效率会高很多。6. 一些掏心窝的话这篇内容从题目分析到模型搭建、代码实现、论文写作、常见问题都过了一遍核心思路是拿到A题先把问题结构吃透再选择合适的算法框架用基线方法验证逻辑再用启发式算法提升解质量最后把思路和数据写清楚。最后分享一个我自己的心得数学建模竞赛表面上比的是模型和算法实际上比的是团队的决策能力和时间管理能力。赛题本身没有标准答案你要做的不是寻找正确的模型而是在有限时间内给出最合理的解决方案并让你的论文说服评阅老师。尤其是AGV调度这类涉及物理约束的问题模型的鲁棒性、假设的合理性、结果的可解释性缺一不可。希望这篇文章能帮你少走一些弯路。如果你在调代码、写论文的过程中遇到具体问题也欢迎私信交流我看到都会回复。数模这条路走着走着你会发现它给你的远不止一张证书。
阅读完成 · 觉得有帮助?