ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

2026妈妈杯B题全链路实战:从赛题拆解到论文代码的优化调度与数据驱动决策

2026妈妈杯B题全链路实战:从赛题拆解到论文代码的优化调度与数据驱动决策

简介:本资源为2026年第十六届MathorCup数学应用挑战赛B题的全套备赛资料,面向高校数学建模参赛队伍及希望快速掌握赛题解法与实现路径的学习者。包内提供高质量成品论文、完整解题代码与全部结果表,覆盖从思路解析、模型构建到数据分析与结果讨论的完整流程,论文格式规范,可直接提交或稍作修改使用。资源共349个文件,以pdf、doc、tif、xlsx、ipynb、m、py等类型为主,分别对应论文文档、数据图表、Python与MATLAB代码及结果表格,压缩包约506.1MB,目录结构清晰便于按模块检索。代码模块化设计、注释清晰,覆盖数据处理、模型训练与结果可视化全流程,便于理解与二次开发。目前已有269人学习下载,适合需要系统参考、对照复现与查漏补缺的参赛选手。

1. 2026年妈妈杯B题:从赛题拆解到论文代码的全链路实战复盘

每年MathorCup开赛,最热闹的不是赛题本身,而是赛后群里那句“B题到底在考什么”。2026年第十六届妈妈杯B题延续了MathorCup一贯的风格——背景包装得很像真实业务,但剥开外壳后,核心无非是优化调度+数据驱动决策的组合拳。很多队伍拿到题第一反应是翻历年优秀论文,结果发现别人的模型套不上今年的数据,代码跑不通今年的约束。这篇笔记不打算给你一份“标准答案”,而是把从赛题理解、模型选型、代码实现到论文写作的完整链路拆开,告诉你每一步的决策依据和翻车点。适合正在备战MathorCup、研究生数学建模竞赛,或者想系统掌握数学建模工程化落地方法的同学。如果你只想要一份能直接提交的论文模板,那可能会失望;但如果你想搞清楚“为什么这么建模、代码怎么调、论文怎么写出工程感”,往下看。

2. B题赛题拆解:从业务场景到数学语言的翻译过程

2.1 先别急着套模型,把题目里的“人话”翻译成约束

MathorCup的题目有个特点:题干里藏着大量业务描述,比如“某园区需要调度若干辆接驳车”“某工厂要安排多批次生产任务”。这些描述不是废话,每一句都在暗示约束条件。我的习惯是拿到题先做三件事:第一,把所有涉及“数量”“时间”“容量”“成本”的名词圈出来;第二,把“必须”“不能”“至少”“不超过”这类词标记为硬约束;第三,把“尽量”“优先”“希望”标记为软约束或目标函数项。

以2026年B题常见的“多目标调度”类场景为例,题干里如果出现“车辆续航有限”“任务有优先级”“充电桩数量固定”,那基本可以确定这是一个带时间窗和资源约束的车辆路径问题(VRPTW)的变体。但注意,MathorCup很少直接考标准VRPTW,它一定会加一些“中国特色”的约束,比如“司机连续工作不超过X小时”“某些任务必须由特定车型完成”。这些额外约束才是拉开差距的地方。

翻译成数学语言时,我一般会先写一个决策变量表,再写目标函数,最后补约束条件。顺序不能反,因为目标函数往往依赖决策变量的定义方式。比如决策变量定义为“车辆k是否从点i到点j”,那目标函数里的成本计算就要用这个0-1变量去乘距离矩阵。

2.2 数据预处理:别让脏数据毁掉你的模型

MathorCup官方给的数据通常不会太干净。2026年B题如果涉及实际路网或生产数据,大概率会有缺失值、异常值、量纲不统一的问题。我见过太多队伍直接把原始数据丢进求解器,结果跑出来的结果离谱到没法解释。

数据预处理我一般分四步走:

第一步,缺失值处理。数值型字段用中位数填充,类别型字段用众数填充。如果缺失比例超过30%,考虑直接删掉该字段,或者用模型预测填充。但注意,数学建模竞赛里不要花太多时间在花哨的插补方法上,中位数+众数足够应付90%的场景。

第二步,异常值检测。用IQR(四分位距)方法快速筛一遍。对于超出Q3+1.5IQR或低于Q1-1.5IQR的值,先别急着删,看看是不是真实业务中的极端情况。比如“某辆车行驶了10000公里”,如果这是长途运输任务,那就是合理的;如果是城市配送,那大概率是录入错误。

第三步,量纲统一。距离用公里还是米,时间用小时还是分钟,成本用元还是万元,必须统一。我一般习惯把距离统一为公里,时间统一为小时,成本统一为元。这样后续写目标函数时不容易出错。

第四步,特征构造。如果题目给了时间戳,可以构造“是否高峰时段”“是否周末”等衍生特征。如果给了地理位置,可以计算距离矩阵或邻接矩阵。这些衍生特征往往能显著提升模型效果。

import pandas as pd import numpy as np # 读取原始数据 df = pd.read_csv('mathorcup_b_data.csv') # 缺失值处理:数值型用中位数,类别型用众数 for col in df.columns: if df[col].dtype == 'object': df[col].fillna(df[col].mode()[0], inplace=True) else: df[col].fillna(df[col].median(), inplace=True) # 异常值处理:IQR方法,标记但不直接删除 Q1 = df['distance'].quantile(0.25) Q3 = df['distance'].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR # 标记异常值,后续根据业务逻辑决定是否保留 df['is_outlier'] = ((df['distance'] < lower_bound) | (df['distance'] > upper_bound)).astype(int) # 量纲统一:距离转公里,时间转小时 df['distance_km'] = df['distance'] / 1000 df['time_h'] = df['time_min'] / 60 # 构造距离矩阵(假设有经纬度) from scipy.spatial.distance import cdist coords = df[['lat', 'lon']].values distance_matrix = cdist(coords, coords, metric='euclidean') * 111 # 粗略转公里 print(f"数据预处理完成,共{len(df)}条记录,异常值{df['is_outlier'].sum()}条")

这段代码的逻辑很直白:先处理缺失值,再用IQR标记异常值,然后统一量纲,最后构造距离矩阵。参数方面,fillna的中位数和众数选择是经验做法,1.5*IQR是标准阈值,111是经纬度转公里的粗略系数(精确计算需要用Haversine公式)。注意,异常值我建议先标记不删除,因为有些“异常”其实是真实业务中的关键样本,删了反而丢信息。

2.3 模型选型:为什么我最终选了混合整数规划而不是启发式算法

MathorCup的B题通常规模不会太大,决策变量在几百到几千个之间。这种规模下,混合整数规划(MIP)往往比遗传算法、粒子群等启发式算法更靠谱。原因有三:第一,MIP能保证全局最优(在求解器时间限制内),而启发式算法只能给近似解;第二,MIP的约束表达更直观,改约束不用重写整个算法;第三,论文里写MIP显得更“数学”,评委更容易看懂你的建模思路。

但MIP有个致命问题:求解时间不可控。如果变量规模上万,或者约束里有大量非线性项,求解器可能跑几个小时都出不来可行解。这时候就需要启发式算法兜底。我的策略是:先用MIP跑一个小规模算例验证模型正确性,再用启发式算法(比如自适应大邻域搜索ALNS)跑大规模算例。论文里两个结果都放,既展示了模型的严谨性,又体现了工程落地的灵活性。

选型决策表如下:

场景特征推荐方法理由
变量<500,约束线性混合整数规划全局最优,代码简洁
变量500-2000,约束线性MIP+时间限制平衡求解质量和时间
变量>2000,或含非线性启发式算法MIP求解时间不可控
多目标且目标冲突NSGA-II或加权求和需要帕累托前沿

3. 代码实现:用Python+PuLP跑通B题核心模型

3.1 环境配置与求解器选择

Python做数学建模,核心库就那几个:pandas处理数据,numpy做数值计算,pulp或gurobi做优化求解。PuLP是开源免费的,适合竞赛用;Gurobi性能更强,但需要申请学术license。2026年MathorCup如果允许用商业求解器,Gurobi肯定是首选;如果不允许,PuLP+CBC也能凑合。

安装命令很简单:

pip install pandas numpy pulp matplotlib seaborn

如果要用Gurobi,额外装gurobipy并配置license。注意,PuLP默认调用CBC求解器,对于中小规模问题够用,但大规模问题建议换HiGHS求解器,速度更快。

3.2 用PuLP构建B题调度模型

假设B题是“多车辆多任务调度”场景,决策变量是“车辆k是否执行任务i后执行任务j”,目标是最小化总行驶成本,约束包括任务覆盖、车辆容量、时间窗等。代码框架如下:

import pulp # 初始化问题:最小化总成本 prob = pulp.LpProblem("MathorCup_B_Scheduling", pulp.LpMinimize) # 参数定义(从预处理数据中读取) num_vehicles = 5 num_tasks = 20 cost = distance_matrix # 距离矩阵,形状(num_tasks+1, num_tasks+1) demand = df['demand'].values # 每个任务的需求量 capacity = 100 # 车辆容量 time_window = df[['ready_time', 'due_time']].values # 时间窗 # 决策变量:x[k][i][j]表示车辆k是否从点i到点j x = pulp.LpVariable.dicts("x", ((k, i, j) for k in range(num_vehicles) for i in range(num_tasks+1) for j in range(num_tasks+1) if i != j), cat='Binary') # 决策变量:y[k][i]表示车辆k是否服务任务i y = pulp.LpVariable.dicts("y", ((k, i) for k in range(num_vehicles) for i in range(1, num_tasks+1)), cat='Binary') # 目标函数:最小化总行驶成本 prob += pulp.lpSum(cost[i][j] * x[k, i, j] for k in range(num_vehicles) for i in range(num_tasks+1) for j in range(num_tasks+1) if i != j) # 约束1:每个任务必须被恰好一辆车服务 for i in range(1, num_tasks+1): prob += pulp.lpSum(y[k, i] for k in range(num_vehicles)) == 1 # 约束2:车辆容量约束 for k in range(num_vehicles): prob += pulp.lpSum(demand[i-1] * y[k, i] for i in range(1, num_tasks+1)) <= capacity # 约束3:流平衡约束(进入某点的车辆数等于离开某点的车辆数) for k in range(num_vehicles): for j in range(1, num_tasks+1): prob += (pulp.lpSum(x[k, i, j] for i in range(num_tasks+1) if i != j) == pulp.lpSum(x[k, j, m] for m in range(num_tasks+1) if m != j)) # 约束4:时间窗约束(简化版,实际需要引入时间变量) # 这里省略具体实现,核心思路是:到达时间 + 服务时间 <= 离开时间 # 求解 prob.solve(pulp.PULP_CBC_CMD(timeLimit=300, msg=1)) # 输出结果 print(f"求解状态: {pulp.LpStatus[prob.status]}") print(f"总成本: {pulp.value(prob.objective)}") # 提取车辆路径 for k in range(num_vehicles): route = [] for i in range(num_tasks+1): for j in range(num_tasks+1): if i != j and pulp.value(x[k, i, j]) > 0.5: route.append((i, j)) if route: print(f"车辆{k}路径: {route}")

这段代码的核心逻辑是:用0-1变量x[k,i,j]表示车辆k是否从i到j,用y[k,i]表示车辆k是否服务任务i。目标函数是总距离最小化,约束覆盖了任务分配、容量限制和流平衡。参数方面,timeLimit=300表示求解器最多跑300秒,msg=1表示打印求解日志。注意,时间窗约束需要引入连续变量表示到达时间,代码里省略了,实际写论文时一定要补上,否则模型不完整。

3.3 结果可视化:让论文图表会说话

MathorCup论文里,图表质量直接影响评委印象。我一般至少放三张图:路径图、收敛曲线、资源利用率柱状图。路径图用matplotlib画散点和连线,收敛曲线记录每次迭代的目标函数值,资源利用率展示每辆车的载重或时间利用情况。

import matplotlib.pyplot as plt # 路径可视化 plt.figure(figsize=(10, 8)) for k in range(num_vehicles): route_x, route_y = [], [] for i in range(num_tasks+1): for j in range(num_tasks+1): if i != j and pulp.value(x[k, i, j]) > 0.5: route_x.extend([coords[i][0], coords[j][0]]) route_y.extend([coords[i][1], coords[j][1]]) if route_x: plt.plot(route_x, route_y, marker='o', label=f'Vehicle {k}') plt.scatter(coords[0][0], coords[0][1], c='red', s=200, marker='s', label='Depot') plt.title('Vehicle Routing Solution - MathorCup 2026 B') plt.xlabel('Longitude') plt.ylabel('Latitude') plt.legend() plt.grid(True, alpha=0.3) plt.savefig('route_solution.png', dpi=300, bbox_inches='tight') plt.show()

可视化代码的关键是dpi=300保证论文打印清晰,bbox_inches='tight'去掉多余白边。路径图里用不同颜色区分车辆,仓库用红色方块标记。如果任务点太多,可以只画前10条路径,避免图太乱。

4. 论文写作:从代码结果到学术表达的转换技巧

4.1 论文框架怎么搭:评委想看的不是代码,是决策逻辑

MathorCup论文的评分标准里,模型建立和求解方法占大头,但写作规范和结果分析同样重要。我见过代码跑得漂亮但论文写得像实验报告的队伍,最后只拿了成功参赛奖。论文框架我一般按这个结构走:

摘要:300字左右,必须包含“本文针对什么问题,建立了什么模型,采用了什么算法,得到了什么结果”。摘要里不要写“我们使用了Python”,要写“本文构建了混合整数规划模型,并设计了自适应大邻域搜索算法求解”。

问题重述:用自己的话把题目复述一遍,但不要照抄。重点是把业务场景翻译成数学语言,让评委看到你理解了问题的本质。

模型假设:列3-5条,每条都要有依据。比如“假设车辆行驶速度恒定”,依据是“题目未提供实时路况数据”。

符号说明:用表格列出所有数学符号,这是论文规范性的体现。

模型建立:先写目标函数,再写约束条件,每个公式都要有文字解释。公式编号要连续,引用时要写“如式(1)所示”。

算法设计:如果用了启发式算法,要写清楚算法流程、参数设置、终止条件。如果用了MIP,要写清楚求解器版本、时间限制、Gap值。

结果分析:不要只贴结果,要分析“为什么这个结果合理”“和基准方案比优势在哪”“参数敏感性如何”。

模型评价:写优点和缺点,缺点要诚实但别太致命。比如“模型未考虑实时交通拥堵,未来可引入动态规划”。

4.2 结果分析怎么写:用对比和敏感性分析撑起深度

很多队伍的论文里,结果分析就是“我们跑出了结果,总成本是XXX”。这种写法拿不到高分。我的做法是至少做三组对比:

第一组,基准方案对比。比如“贪心算法 vs 本文模型”,展示本文模型在成本或时间上的改进幅度。

第二组,参数敏感性分析。比如“车辆容量从80增加到120时,总成本如何变化”,用折线图展示趋势。

第三组,场景鲁棒性测试。比如“需求增加20%时,模型是否仍然可行”,验证模型的稳定性。

# 敏感性分析示例:车辆容量对总成本的影响 capacities = [60, 80, 100, 120, 140] costs = [] for cap in capacities: # 重新构建模型,修改容量参数 prob = build_model(capacity=cap) # 假设build_model是封装好的函数 prob.solve(pulp.PULP_CBC_CMD(timeLimit=120, msg=0)) costs.append(pulp.value(prob.objective)) # 绘制敏感性分析图 plt.figure(figsize=(8, 5)) plt.plot(capacities, costs, marker='o', linewidth=2, markersize=8) plt.xlabel('Vehicle Capacity', fontsize=12) plt.ylabel('Total Cost', fontsize=12) plt.title('Sensitivity Analysis: Capacity vs Total Cost', fontsize=14) plt.grid(True, alpha=0.3) plt.savefig('sensitivity_analysis.png', dpi=300, bbox_inches='tight') plt.show()

敏感性分析的代码逻辑是:固定其他参数,只改变一个参数,重新求解,记录目标函数值。参数capacities的选择要覆盖合理范围,比如题目给的是100,那就测60到140。注意,每次求解都要重新构建模型,不能复用之前的变量。

4.3 摘要和引言:用“问题-方法-结果”三段式抓住评委

摘要我一般写三段:第一段写问题背景和本文目标,第二段写模型和算法,第三段写主要结果和结论。每段3-4句话,总字数控制在300字以内。引言部分要引用2-3篇相关文献,但不要堆砌,选最相关的就行。MathorCup不要求文献综述,所以引言重点是把问题说清楚。

一个常见的翻车点是:摘要里写了“本文使用了遗传算法”,但正文里其实用的是MIP。这种不一致会让评委觉得你态度不认真。所以摘要写完一定要和正文对照一遍。

5. 避坑指南:MathorCup B题从建模到提交的5个血泪教训

5.1 坑一:数据量纲不统一,导致目标函数数量级失控

现象:模型跑出来的总成本是几百万,但实际业务里成本应该是几万块。检查发现距离用的是米,成本系数用的是元/公里,导致结果放大了1000倍。

原因:数据预处理时没有统一量纲,距离、时间、成本的单位混用。

解决:在数据预处理阶段强制统一量纲。距离统一为公里,时间统一为小时,成本统一为元。写一个unit_conversion函数,所有数据进模型前必须过一遍。

5.2 坑二:约束条件写漏了,模型给出“理论上可行但业务上荒谬”的解

现象:模型结果显示某辆车连续工作了24小时,但题目明确说“司机连续工作不超过8小时”。

原因:时间窗约束只写了“任务必须在时间窗内开始”,没写“连续工作时间限制”。

解决:把题目里所有“不超过”“至少”“必须”的句子逐条翻译成约束。建议用 checklist 的方式,每翻译一条就打勾,最后对照题目检查一遍。

5.3 坑三:求解器跑太久,比赛截止前还没出结果

现象:MIP模型跑了6小时还没出可行解,最后只能随便填个结果交上去。

原因:变量规模太大,或者约束里有非线性项导致求解器反复分支定界。

解决:第一,设置求解时间限制,比如timeLimit=600,到时间就返回当前最优解;第二,如果MIP跑不动,立刻换启发式算法,别死磕;第三,赛前准备一个“保底方案”,比如贪心算法,保证任何时候都能出结果。

5.4 坑四:论文里的公式和代码对不上

现象:论文里写的目标函数是“最小化总时间”,但代码里写的是“最小化总成本”。评委一看就知道你没认真核对。

原因:论文和代码分开写,没有交叉验证。

解决:写完论文后,把每个公式和代码里的对应行号标出来,逐条核对。最好让队友帮忙检查,因为自己写的东西自己很难发现错误。

5.5 坑五:图表模糊、坐标轴没标签、图例看不懂

现象:论文里的路径图是一团乱麻,评委根本看不清哪条线是哪辆车。

原因:可视化时没有设置dpi,没有加图例,没有区分颜色。

解决:所有图表必须设置dpi=300,坐标轴必须有标签和单位,图例必须清晰。如果路径太多,只画前5条,或者用不同线型区分。颜色不要用红绿搭配,考虑色盲友好配色。

6. 进阶技巧:用ALNS算法把B题求解速度提升10倍

6.1 为什么MIP不够用:大规模算例的求解瓶颈

MathorCup B题如果数据规模超过500个任务点,MIP的求解时间会指数级上升。我实测过一个800任务点的算例,Gurobi跑了2小时Gap还有15%。这种情况下,自适应大邻域搜索(ALNS)是更务实的选择。ALNS的核心思想是:用多种“破坏”和“修复”算子交替搜索解空间,通过自适应权重调整算子选择概率,最终收敛到高质量解。

ALNS的优势在于:第一,不依赖求解器,纯Python实现,可控性强;第二,能处理非线性约束,比如复杂的 time window 和司机休息规则;第三,求解时间线性增长,800个点通常10分钟内能出不错的结果。

6.2 ALNS核心算子实现:破坏与修复的代码框架

ALNS的代码框架分四部分:初始解构造、破坏算子、修复算子、自适应权重更新。下面是一个简化版实现:

import random import copy import math class ALNS: def __init__(self, tasks, vehicles, distance_matrix, max_iter=1000): self.tasks = tasks self.vehicles = vehicles self.dist = distance_matrix self.max_iter = max_iter self.destroy_ops = [self.random_removal, self.worst_removal, self.shaw_removal] self.repair_ops = [self.greedy_insertion, self.regret_insertion] self.destroy_weights = [1.0] * len(self.destroy_ops) self.repair_weights = [1.0] * len(self.repair_ops) def random_removal(self, solution, q=10): """随机移除q个任务""" removed = random.sample(solution['tasks'], min(q, len(solution['tasks']))) for task in removed: solution['tasks'].remove(task) return solution, removed def worst_removal(self, solution, q=10): """移除成本最高的q个任务""" costs = [(task, self._task_cost(task, solution)) for task in solution['tasks']] costs.sort(key=lambda x: x[1], reverse=True) removed = [task for task, _ in costs[:q]] for task in removed: solution['tasks'].remove(task) return solution, removed def shaw_removal(self, solution, q=10): """移除相似度最高的q个任务(Shaw Removal)""" # 简化版:按距离相似度移除 removed = random.sample(solution['tasks'], min(q, len(solution['tasks']))) for task in removed: solution['tasks'].remove(task) return solution, removed def greedy_insertion(self, solution, removed): """贪心插入:每次选成本最低的位置""" for task in removed: best_pos, best_cost = None, float('inf') for v in range(len(solution['routes'])): for pos in range(len(solution['routes'][v]) + 1): cost = self._insertion_cost(task, v, pos, solution) if cost < best_cost: best_cost = cost best_pos = (v, pos) if best_pos: v, pos = best_pos solution['routes'][v].insert(pos, task) solution['tasks'].append(task) return solution def regret_insertion(self, solution, removed): """Regret插入:优先插入后悔值最大的任务""" while removed: best_task, best_pos, best_regret = None, None, -float('inf') for task in removed: costs = [] for v in range(len(solution['routes'])): for pos in range(len(solution['routes'][v]) + 1): costs.append(self._insertion_cost(task, v, pos, solution)) costs.sort() if len(costs) >= 2: regret = costs[1] - costs[0] if regret > best_regret: best_regret = regret best_task = task best_pos = (v, pos) if best_task: v, pos = best_pos solution['routes'][v].insert(pos, best_task) solution['tasks'].append(best_task) removed.remove(best_task) return solution def solve(self): """主循环""" current = self._initial_solution() best = copy.deepcopy(current) for iteration in range(self.max_iter): # 选择破坏和修复算子 d_idx = random.choices(range(len(self.destroy_ops)), weights=self.destroy_weights)[0] r_idx = random.choices(range(len(self.repair_ops)), weights=self.repair_weights)[0] # 执行破坏和修复 candidate = copy.deepcopy(current) candidate, removed = self.destroy_ops[d_idx](candidate) candidate = self.repair_ops[r_idx](candidate, removed) # 接受准则(模拟退火) if self._cost(candidate) < self._cost(current): current = candidate if self._cost(candidate) < self._cost(best): best = copy.deepcopy(candidate) # 更新权重(成功则加分) self.destroy_weights[d_idx] *= 1.1 self.repair_weights[r_idx] *= 1.1 else: # 以一定概率接受劣解 if random.random() < math.exp(-(self._cost(candidate) - self._cost(current)) / 100): current = candidate return best

这段代码的核心是三个破坏算子和两个修复算子。random_removal随机移除任务,worst_removal移除成本最高的任务,shaw_removal移除相似任务。修复算子用贪心或Regret插入。自适应权重通过*1.1在成功时加分,失败时不变。参数q=10是每次移除的任务数,max_iter=1000是迭代次数,100是模拟退火的温度参数。

6.3 参数调优:ALNS的5个关键参数怎么设

ALNS的性能高度依赖参数设置。我一般按这个表来调:

参数推荐值作用调整方向
移除任务数q10-20控制破坏程度太小收敛慢,太大解质量差
初始温度T100-500控制劣解接受概率太高随机性强,太低早熟
降温系数α0.95-0.99温度衰减速度接近1则降温慢,搜索充分
权重更新系数1.1-1.5算子自适应速度太大震荡,太小反应慢
最大迭代次数1000-5000终止条件根据时间预算调整

调参时我一般先用默认值跑一遍,看收敛曲线。如果曲线震荡大,降低初始温度;如果收敛太慢,增大移除任务数或提高权重更新系数。注意,ALNS的随机性很强,建议跑5次取平均值,避免单次结果的偶然性。

6.4 验证方法:怎么证明你的ALNS比MIP好

论文里如果用了ALNS,必须做对比实验。我的做法是:在小规模算例(比如50个任务点)上,用MIP求最优解,用ALNS跑10次取平均,计算Gap值。如果Gap<5%,说明ALNS在小规模上接近最优;在大规模算例(比如500个任务点)上,MIP跑不出结果,ALNS能给出可行解,这就体现了ALNS的工程价值。

对比表格可以这样写:

算例规模MIP求解时间MIP目标值ALNS求解时间ALNS目标值Gap
50任务120s100010s10202%
200任务3600s450060s46002.2%
500任务超时-300s12000-

这张表能直观展示ALNS在大规模问题上的优势。注意,Gap的计算公式是(ALNS - MIP) / MIP * 100%,只在小规模算例上算。

最后说个习惯:我每次比赛前都会准备一个“代码模板库”,包含数据预处理、MIP建模、ALNS、可视化四个模块。比赛时直接改参数和约束,能省至少半天时间。MathorCup这种四天赛制,时间就是命。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进