简介:这份资源面向备战中国研究生数学建模竞赛的研究生,聚焦2023年华为杯B题,提供从思路梳理到代码实现再到论文写作的完整参考方案。压缩包共19个文件,约24.01MB,包含9个PDF论文与解析文档、8个MATLAB脚本(.m)、1个说明文档(.md)和1个Word文档,覆盖DFT类矩阵整数分解逼近的建模思路、复杂度计算、数据绘图与各问题求解代码,便于读者对照赛题逐步复现与验证。已有227人学习下载,适合需要系统掌握赛题解法、借鉴论文结构与代码框架的参赛者,也可作为数学建模方法训练与MATLAB编程实践的参考素材。
1. 从赛题到可复现方案:华为杯B题到底在考什么
每年华为杯开赛,B题总是被讨论得最多的一档。2023年的B题延续了华为杯一贯的风格——给一个贴近工程实际的场景,丢几组带噪声的数据,要求你在有限时间内完成从建模、求解到论文呈现的完整闭环。很多人拿到题第一反应是“这题到底要我算什么”,第二反应是“我该用什么模型”。但真正拉开差距的,不是模型有多花哨,而是你有没有把问题拆成可计算的结构,有没有让每一步都有据可查。
这篇笔记面向三类人:第一次参加华为杯、想搞清楚B题完整解题链路的研究生;已经有过参赛经验、但每次卡在“模型能跑通、论文写不出”的选手;以及想把这套方法迁移到其他数学建模竞赛的从业者。我会按“读题拆解→数据预处理→模型选型→求解实现→论文组织”的顺序,把每个环节的关键决策点、参数设置和常见翻车点讲清楚。代码部分用Python实现,论文部分给出可套用的结构模板。
需要提前说明的是,数学建模竞赛没有唯一正确答案,但有明确的评分逻辑。评委看的是你的假设是否合理、模型是否自洽、结果是否可验证、论文是否 readable。所以整篇内容的核心不是教你“标准答案”,而是帮你建立一套可复用的解题框架。
2. 赛题拆解与数据预处理:把模糊描述变成可计算问题
2.1 从题目文字中提取约束条件和目标函数
华为杯B题通常会给一段200到500字的背景描述,里面混杂着业务场景、数据说明和若干小问。新手最容易犯的错误是读完题直接打开MATLAB或Python开始试模型,结果做到一半发现漏了约束条件。我的习惯是先把题目打印出来,用三种颜色的笔分别标注:目标(要优化什么)、约束(不能违反什么)、数据(给了什么、缺什么)。
以2023年B题为例,题目涉及多阶段决策与资源分配问题。第一遍读的时候,我会在草稿纸上写出这样的结构:
- 决策变量:每个阶段每个对象分配多少资源
- 目标函数:总收益最大或总成本最小
- 约束条件:资源总量限制、阶段间转移关系、边界条件
- 数据输入:历史数据表、参数表、初始状态
这一步不需要任何代码,但决定了后面所有工作的方向。如果题目有多个小问,还要判断小问之间是递进关系还是独立关系。递进关系意味着前一问的输出是后一问的输入,这时候第一问的模型精度会直接影响后续所有结果。
提示:读题阶段至少花30分钟,把每个小问的关键词圈出来。如果某个词你不确定含义,先按最保守的方式理解,并在论文假设中明确写出来。
2.2 数据清洗的四个必做步骤
数学建模竞赛给的数据几乎不可能是干净的。缺失值、异常值、量纲不统一、时间戳格式混乱,这些都是常态。下面这段代码是我常用的数据预处理模板,适用于大多数B题场景:
import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler # 1. 读取数据,注意编码格式和分隔符 df = pd.read_csv('data.csv', encoding='utf-8', sep=',') # 2. 缺失值处理:数值型用中位数填充,类别型用众数填充 num_cols = df.select_dtypes(include=[np.number]).columns cat_cols = df.select_dtypes(include=['object']).columns for col in num_cols: df[col].fillna(df[col].median(), inplace=True) for col in cat_cols: df[col].fillna(df[col].mode()[0], inplace=True) # 3. 异常值检测:用IQR方法标记,不直接删除,先记录 Q1 = df[num_cols].quantile(0.25) Q3 = df[num_cols].quantile(0.75) IQR = Q3 - Q1 outlier_mask = ((df[num_cols] < (Q1 - 1.5 * IQR)) | (df[num_cols] > (Q3 + 1.5 * IQR))) print("异常值数量:\n", outlier_mask.sum()) # 4. 标准化:对参与距离计算的数值列做Z-score标准化 scaler = StandardScaler() df[num_cols] = scaler.fit_transform(df[num_cols])这段代码的逻辑说明:缺失值填充选择中位数而不是均值,是因为中位数对异常值更鲁棒;异常值先用IQR标记而不是直接删除,是因为有些“异常”可能是真实业务中的极端情况,删掉反而丢失信息;标准化只对需要计算距离或参与梯度下降的列做,如果后续用树模型,标准化不是必须的。
参数方面,IQR的1.5倍是统计学惯例,如果数据本身波动极大,可以放宽到3倍。标准化的方式也可以换成Min-Max,取决于后续模型对数据分布的假设。
2.3 特征工程的取舍原则
B题的数据量通常不会太大,特征数量可能就十几个到几十个。这时候不要盲目做PCA降维,因为降维后的主成分很难解释,论文里写出来评委也看不懂。我的原则是:能解释的特征保留,不能解释的特征要么找到业务含义,要么删掉。
如果题目涉及时间序列,可以构造滞后特征和滑动窗口统计量。如果涉及分类,可以构造交叉特征。但每构造一个特征,都要在论文里写清楚“为什么构造它”和“它代表了什么业务含义”。没有业务解释的特征,在答辩环节会被问住。
3. 模型选型与求解:从评价模型到优化模型的完整链路
3.1 评价类问题的模型选择:AHP、熵权法与TOPSIS的适用边界
华为杯B题经常出现“评价多个方案”或“排序多个对象”的小问。这类问题的核心是确定权重和综合得分。常见的三种方法各有适用场景:
| 方法 | 适用场景 | 数据要求 | 缺点 |
|---|---|---|---|
| AHP层次分析法 | 指标少、专家判断可获取 | 判断矩阵一致性检验通过 | 主观性强 |
| 熵权法 | 指标多、数据完整 | 需要足够样本计算信息熵 | 忽略指标间相关性 |
| TOPSIS | 多目标决策 | 需要正向化处理和标准化 | 权重需额外确定 |
实际比赛中,我一般会用熵权法确定权重,再用TOPSIS计算综合得分。这样既有客观数据支撑,又能给出清晰的排序结果。如果题目明确要求考虑专家意见,才会引入AHP,并且会把AHP和熵权法的结果做一致性对比,增加论文的说服力。
import numpy as np def entropy_weight(data): """熵权法计算权重 data: shape (n_samples, n_indicators) """ # 正向化处理:这里假设所有指标已经正向化 # 归一化 data_norm = data / data.sum(axis=0) # 计算信息熵 n = data.shape[0] k = 1 / np.log(n) entropy = -k * np.sum(data_norm * np.log(data_norm + 1e-12), axis=0) # 计算权重 d = 1 - entropy weights = d / d.sum() return weights # 示例:5个方案,4个指标 data = np.array([[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8]]) w = entropy_weight(data) print("权重:", w)这段代码的关键在于归一化方式。如果指标有负值,需要先做平移处理。信息熵公式中的1e-12是为了防止log(0)报错。权重计算出来后,要检查是否满足“重要指标权重大”的直觉,如果不符合,说明数据本身有问题或者正向化方向搞反了。
3.2 优化类问题的建模:线性规划、整数规划与启发式算法
B题的另一大类是优化问题,比如资源分配、路径规划、调度排程。这类问题的标准解法是:先判断问题规模,小规模用精确算法(线性规划、整数规划),大规模用启发式算法(遗传算法、粒子群、模拟退火)。
判断标准很简单:如果决策变量数量在1000以内,且约束条件都是线性的,直接用scipy.optimize.linprog或pulp求解。如果变量超过1000或者约束非线性,再考虑启发式算法。
from scipy.optimize import linprog # 线性规划示例:最小化成本 # min c^T x # s.t. A_ub x <= b_ub # A_eq x = b_eq # bounds c = [2, 3, 1] # 目标函数系数 A_ub = [[1, 1, 1], [1, 2, 0]] # 不等式约束左侧 b_ub = [10, 8] # 不等式约束右侧 A_eq = [[1, 0, 1]] # 等式约束左侧 b_eq = [5] # 等式约束右侧 bounds = [(0, None), (0, None), (0, None)] # 变量范围 res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method='highs') print("最优值:", res.fun) print("最优解:", res.x)参数说明:method='highs'是scipy目前推荐的求解器,比默认的单纯形法更快更稳定。bounds用来设置变量的上下界,如果某个变量必须为整数,需要改用pulp或cvxpy的整数规划接口。
如果问题规模大,用遗传算法时要注意编码方式和适应度函数的设计。编码方式决定了搜索空间的大小,适应度函数决定了选择压力。我一般用实数编码,适应度函数直接取目标函数的倒数或负值。
3.3 预测类问题的模型选择:从回归到时间序列
如果B题涉及预测,比如预测未来需求量、预测某个指标的变化趋势,首先要判断是回归问题还是时间序列问题。回归问题用sklearn的线性回归、随机森林、XGBoost;时间序列问题用ARIMA、LSTM或Prophet。
from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error # 假设X是特征,y是目标 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) model = RandomForestRegressor(n_estimators=100, max_depth=10, random_state=42) model.fit(X_train, y_train) y_pred = model.predict(X_test) rmse = mean_squared_error(y_test, y_pred, squared=False) print(f"RMSE: {rmse:.4f}") # 特征重要性 importance = pd.Series(model.feature_importances_, index=X.columns) print(importance.sort_values(ascending=False))参数说明:n_estimators=100是起点,如果数据量大可以增加到200-500;max_depth=10用来控制过拟合,如果训练集R²远高于测试集R²,说明深度太大。特征重要性输出后,要检查是否和业务直觉一致,如果某个无关特征重要性很高,可能是数据泄露。
注意:预测类问题一定要划分训练集和测试集,不能在全量数据上训练后再报告拟合优度。评委看到没有测试集验证的预测结果,会直接扣分。
4. 论文写作与结果呈现:让评委在10分钟内看懂你的方案
4.1 论文结构的标准模板与各章节字数分配
华为杯论文没有官方模板,但获奖论文的结构高度一致。我整理了一个经过验证的框架:
| 章节 | 内容 | 建议字数 |
|---|---|---|
| 摘要 | 问题、方法、结果、结论 | 400-600 |
| 问题重述 | 用自己的话复述问题 | 300-500 |
| 模型假设 | 3-5条合理假设 | 200-300 |
| 符号说明 | 表格形式 | 不占字数 |
| 模型建立与求解 | 核心部分 | 3000-5000 |
| 结果分析 | 灵敏度分析、误差分析 | 800-1200 |
| 模型评价与推广 | 优点、缺点、改进方向 | 400-600 |
摘要是最重要的部分,评委如果只看一段,就是摘要。摘要必须包含:每问用了什么方法、得到了什么具体结果(带数字)、结论是什么。不要写“本文建立了模型”这种空话,要写“针对问题一,建立了以总成本最小为目标的整数规划模型,求解得到最优成本为XX万元,比基准方案降低XX%”。
4.2 图表规范:什么样的图能让评委加分
数学建模论文的图分三类:流程图、数据图、结果图。流程图用Visio或draw.io画,不要用截图;数据图用matplotlib或seaborn,注意坐标轴标签、单位、图例;结果图要突出关键数字,可以用表格辅助。
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文显示 plt.rcParams['axes.unicode_minus'] = False fig, ax = plt.subplots(figsize=(8, 5)) ax.plot(x, y, marker='o', label='实际值') ax.plot(x, y_pred, marker='s', linestyle='--', label='预测值') ax.set_xlabel('时间(天)') ax.set_ylabel('需求量(件)') ax.set_title('需求量预测对比') ax.legend() ax.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('result.png', dpi=300)参数说明:dpi=300保证打印清晰,tight_layout()防止标签被截断。如果论文要求黑白打印,要用不同线型和标记区分,不能只靠颜色。
4.3 灵敏度分析与模型验证的写法
灵敏度分析是区分国赛和省赛论文的关键。做法很简单:选一个关键参数,让它上下浮动10%、20%,观察结果变化。如果结果变化不大,说明模型稳定;如果变化剧烈,说明这个参数需要精确估计。
写法上,先给出参数变化表,再画一张折线图,最后用一段话解释“为什么这个参数敏感”或“为什么模型对参数不敏感”。不要只贴图不解释,评委要看的是你的分析逻辑。
5. 避坑与排查:历年B题最常见的5个翻车点
5.1 假设不合理导致模型全盘失效
现象:论文写完后被队友或导师问“你这个假设凭什么成立”,答不上来。原因:假设是为了简化问题,但简化过度会导致模型脱离实际。比如假设“需求量服从正态分布”,但数据明显右偏。解决:每条假设都要有依据——要么来自题目原文,要么来自数据检验,要么来自参考文献。假设数量控制在3到5条,太多显得心虚,太少显得考虑不周。
5.2 数据泄露导致预测结果虚高
现象:测试集R²达到0.99,但换一组数据就崩了。原因:特征里包含了目标变量的未来信息,或者标准化时用了全量数据。解决:划分训练集和测试集后,标准化参数只能从训练集计算。时间序列问题要用滚动预测,不能用未来数据训练。
5.3 求解器报错但不知道去哪查
现象:linprog返回status=2,不知道什么意思。原因:没有看官方文档的返回值说明。解决:scipy的linprog返回值中,status=0表示最优,status=2表示不可行,status=3表示无界。不可行说明约束矛盾,无界说明缺少上界约束。养成看文档的习惯,比问别人快。
5.4 论文排版混乱导致扣分
现象:公式编号不连续、图表跨页、参考文献格式不统一。原因:最后一天才排版,时间不够。解决:从第一天就用LaTeX或Word模板,公式用MathType或LaTeX公式编辑器。图表标题放在图下方、表上方,这是学术规范。
5.5 代码和论文结果对不上
现象:论文里写最优值是100,代码跑出来是120。原因:改了代码没更新论文,或者论文里的数字是手算的。解决:所有结果数字必须从代码输出直接复制,不要手动输入。最后提交前,用一份检查清单逐项核对:每个数字、每张图、每个表格。
6. 从竞赛到实战:把B题方法迁移到真实工程问题
竞赛结束后,这套方法并不会浪费。真实工程中的资源分配、排程优化、效果评估,本质上和B题是同一类问题。区别在于:竞赛有明确的时间边界和评分标准,工程问题没有标准答案,但要求可落地、可维护、可解释。
我自己的习惯是,每做完一道B题,把代码整理成一个可复用的模块:数据预处理函数、模型训练函数、结果可视化函数。下次遇到类似问题,直接调用,只需要改参数和输入数据。这样积累两三年,你就有了自己的工具箱。
最后一个具体技巧:论文写完后,让一个没参与比赛的同学读一遍摘要和模型部分,如果他能在10分钟内说出你做了什么、结果是什么,说明论文合格;如果他问“这个符号什么意思”,说明符号说明没写好。这个测试比任何自检清单都有效。
希望帮到你。
本文还有配套的精品资源,点击获取