
简介2024年数学建模国赛C题代码与数据包面向数学建模竞赛选手、数据分析学习者与备赛团队聚焦国赛C题的完整求解流程。资源包共51个文件压缩后约2.4MB以17个Python脚本为核心完整覆盖数据读取、预处理、异常值检测、相关性分析、聚类分析、遗传算法寻优及线性规划求解等关键环节同时包含9个xlsx结果表格、3个csv中间数据和13个png图片图片涵盖柱状图、箱线图、热力图、QQ图、散点图矩阵等多种可视化便于直观对比与检查。包内另附README说明与开发环境配置目录按题目问题一、问题二、问题三分模块组织清晰易读可逐步复现。已有5924人加入学习不论用于备赛复盘还是算法复现都能快速获得从数据到结论的完整参考。 2024年国赛C题相信让不少队伍印象深刻。题目给了一个乡村的真实地块和作物种植数据让你定2024到2030年的种植策略中间还加了预期销售量、滞销降价这些不确定性条件。这题乍一看像数据分析骨子里其实是一道运筹优化题。我们队伍当时用线性规划混蒙特卡洛模拟的方式解完了全部四问拿了省奖。这篇文章把当时用到的代码逻辑、数据预处理思路以及踩过的坑全部过一遍给后面备赛的同学一个可以直接抄作业的参考。1. 赛题拆解种地问题的本质是决策优化1.1 题目到底在问什么C题的数据结构其实非常清晰若干块地分平原、丘陵、山地三类几十种作物每种作物有对应的亩产、种植成本、销售价格另外给了2023年的种植情况作为初始条件。第一问要求在2024到2030年间逐年制定种植方案后面几问逐步加入预期销售量下降滞销降价销售亩产波动等条件。很多队伍在第一问就开始放飞自我上来就用灰色预测、神经网络预测未来七年的亩产和价格。这个方向其实偏了。题目并没有让你预测市场它给的是决策问题核心是每一年、每一块地种什么、种多少、怎么分配才能让收益最大。预测只是辅助手段决策模型才是主体。1.2 为什么线性规划是基本盘这类资源有限、约束明确、目标清晰的问题最合适的数学工具就是线性规划。目标函数是总收益最大化约束包括地块面积、作物种植条件、轮作要求、市场需求上限等决策变量就是每种作物在不同地块上的种植面积。我见过不少队伍第一问就用遗传算法、粒子群这些启发式算法结果解出来还不一定比线性规划好。原因很简单线性规划有成熟的求解器能保证全局最优启发式算法只能逼近最优还浪费时间调参。第一问这种确定性模型线性规划就是标准答案。真正用到启发式算法的地方应该在后面处理不确定性时而且也只是辅助。2. 数据清洗九成队伍在前处理就崩了2.1 原始数据的坑在哪里C题的数据包看起来规整真上手处理时坑不少数据分散在多个Excel表地块信息、作物信息、销售数据、历史种植情况是分开的需要自己通过编号关联。字段命名不统一同样的作物在作物名称和作物编号两套体系里对不上。亩产和成本只有2023年一年的数据但题目要求规划到2030年后面年份的产量和成本需要自己给出合理性假设不能空着。2023年的种植数据存在同一块地种过多种作物部分地块闲置的情况这些都要作为初始状态处理直接影响轮作约束。第一条建议拿到数据第一时间做原始备份存一个data_raw目录所有修改过的数据放data_processed目录。别问我为什么强调这个我们队中途把原始表改坏了要不是有备份后面根本没法做。2.2 我的清洗逻辑与代码整个预处理我分四步走第一步把Excel里所有Sheet读出来统一转成DataFrame字段名统一为英文小写加下划线。import pandas as pd file_path data_raw/附件.xlsx sheets pd.read_excel(file_path, sheet_nameNone) for sheet_name, df in sheets.items(): df.columns [col.strip().lower().replace( , _) for col in df.columns] print(fSheet: {sheet_name}, shape: {df.shape}) display(df.head())第二步把地块信息和作物信息整理成两张主表。地块表保留地块编号、类型、面积作物表保留作物编号、名称、类型粮食/蔬菜/食用菌、亩产、成本、售价。这里注意把亩产和成本统一成每亩口径避免后续计算时单位混乱。第三步把2023年种植情况表和主表关联检查是否存在地块编号在种植情况表里出现但主表里没有的情况。plant_2023 pd.read_excel(file_path, sheet_name2023年种植情况) plant_2023 plant_2023.merge(land_df, on地块编号, howleft) plant_2023 plant_2023.merge(crop_df, on作物编号, howleft) print(地块匹配缺失, plant_2023[land_type].isna().sum()) print(作物匹配缺失, plant_2023[crop_name].isna().sum())第四步处理2024年之后的参数。题目没有给未来七年亩产和成本的变化这就要做假设。我们当时采用维持2023年水平、每年允许小幅波动的方式把亩产和成本设计成以2023年为基准的随机变量为后面的蒙特卡洛模拟留好接口。这一步看起来不起眼但决定了后面所有计算的可行性一定要提前想清楚。3. 核心模型与代码实现从约束到求解3.1 决策变量和约束怎么写线性规划建模的关键是设计好决策变量和约束条件。以第一问为例决策变量设定为x[year][land][crop]第year年地块land上种植crop的面积单位亩连续变量。y[land][crop]每块地种哪种作物0-1变量用于轮作约束。目标函数是七年总收益最大化max sum( (亩产 * 售价 - 成本) * x[year][land][crop] )约束条件分四类面积约束每块地种植的总面积不能超过该地块面积。这个最简单直接对每个地块求和。单一作物约束同一块地同一年只能种一种作物题目明确地块不能混种用y变量和x变量的关系来约束。轮作约束同一块地相邻两年不能种同一种作物特别是豆科作物要避免重茬。市场需求约束每种作物全年总产量不能超过题目给出的预期销售量超过部分只能降价销售或滞销。第一问里市场约束还不是特别强到了第三问加了超过预期销售量部分降价50%的条件后这个约束直接变成收益函数的分段处理点。3.2 用pulp写线性规划我们用的求解工具是pulp免费、纯Python、安装简单对这类中规模线性规划完全够用。为什么不选scipy的linprog因为linprog写起来不够直观约束全部要按矩阵形式组织出bug时很难排查。pulp用表达式拼接变量名和约束条件直接对应建模思路调试体验好得多。from pulp import LpProblem, LpMaximize, LpVariable, LpStatus, value prob LpProblem(Crop_Planning, LpMaximize) # 决策变量 x {} for t in years: for land in lands: for crop in crops: x[(t, land, crop)] LpVariable( fx_{t}_{land}_{crop}, lowBound0 ) # 目标函数 prob lpSum( (yield_data[crop] * price_data[crop] - cost_data[crop]) * x[(t, land, crop)] for t in years for land in lands for crop in crops ) # 面积约束 for t in years: for land in lands: prob lpSum( x[(t, land, crop)] for crop in crops ) land_area[land], farea_{t}_{land} # 求解 prob.solve() print(Status:, LpStatus[prob.status]) print(Optimal Value:, value(prob.objective))这里有个细节要注意crops列表必须提前过滤。不是所有作物都能种在所有地块类型上比如水稻只能种在水浇地或平原水田不能因为模型里没写就允许在山地种。这个约束叫土地适宜性约束在建模时就得把每种作物允许种植的地块类型列一个字典。3.3 不确定性处理蒙特卡洛模拟套壳第一问是确定性模型但从第二问开始亩产和预期销售量都变成不确定的了。这时候纯线性规划就不够用了我采用的方案是蒙特卡洛模拟外壳 线性规划内核设定亩产和预期销售量的概率分布。比如亩产服从以2023年为均值、标准差为10%均值的正态分布。生成N组随机场景每组场景都跑一次线性规划得到该场景下的最优种植方案和收益。统计所有场景下各方案的收益分布选择期望收益最大、同时方差可控的方案作为最终答案。import numpy as np n_scenarios 500 results [] for i in range(n_scenarios): np.random.seed(i) yield_scale np.random.normal(1.0, 0.1, sizelen(crops)) demand_scale np.random.uniform(0.8, 1.0, sizelen(crops)) # 在目标函数里用缩放后的亩产和销量系数 prob build_model( yield_scaleyield_scale, demand_scaledemand_scale ) prob.solve() results.append(value(prob.objective)) print(fMean: {np.mean(results):.2f}) print(fStd: {np.std(results):.2f}) print(f5% quantile: {np.percentile(results, 5):.2f})500次模拟在我们的数据规模下跑完差不多5到8分钟属于可接受范围。再多场景收益分布的稳定性变化不大没必要硬撑到5000次。4. 方案验证与结果呈现4.1 敏感性分析怎么做才不白做评阅老师很看重结果的可信度敏感性分析是证明方案稳健性的关键论据。我当时做了两组敏感性分析第一组是价格敏感性。把每种作物的售价上下浮动10%看总收益变化。结果发现如果豆类价格下降10%方案里豆类种植面积会明显减少说明豆类是边际收益比较敏感的作物。第二组是预期销售量敏感性。把各作物预期销售量同时乘0.8、0.9、1.0、1.1观察总收益变化。这组分析在第三问里特别有用因为题目本身就问预期销售量下降对种植方案的影响这个结果可以直接作为论据。敏感性分析的代码不用单独写一套模型只需要在求解时循环修改参数把每次的目标函数值记录下来。4.2 论文图表怎么做才亮眼图表是C题得分的重要一环。我们总结下来这三种图最实用种植结构堆叠图每年各类作物的种植面积堆叠柱状图一眼看出种植结构随时间的变化。地块利用热力图横轴是年份纵轴是地块编号颜色表示该地块当年种的作物类型直观展示轮作方案。收益分布直方图蒙特卡洛模拟的所有情景收益分布图画出来以后可以很自然地在论文里写方案在95%置信水平下收益区间为XX到XX万元。画图工具就用matplotlib配上中文字体设置注意保存成高分辨率PDF插入论文时不要压缩得太厉害。这里再多说一句图表下面一定要配一句看图说话解释这个图能说明什么问题。不能把图一放就完事评阅老师没时间猜你想表达什么。5. 常见问题与避坑记录5.1 新手最容易踩的五个坑这题我们踩了不少坑也看了不少其他队伍分享的报错记录整理成一张速查表问题原因解决办法求解器返回Infeasible约束条件写死地块类型与作物不匹配检查地块适宜性字典去掉不合理的作物-地块组合目标函数值异常小单位不统一把亩产按公斤、售价按元/斤混算所有参数统一为每亩、每公斤再进入模型部分作物永远不种边际收益率低被约束条件挤出方案合理即可不要强行加入论文里解释原因蒙特卡洛模拟结果波动大随机种子没固定每次都不同用np.random.seed(i)固定每个情景的种子数据表合并后出现NaN编号对不上比如2023年表里有多余地块打印缺失行单独处理不要直接dropna5.2 备赛的几点心得先说时间分配。C题从拿到题目到提交只有72小时左右我们队的时间配比是前8小时读题和数据清洗20小时建模和写代码后面全部用来写论文和画图。数据清洗一旦拖延整个节奏就崩了。再说工具。只要不是特别大的数据Pandas加Pulp完全够用。不要一开始就上复杂的框架把基本流程跑通再谈优化。最后说备份。所有代码每隔一两个小时就提交一次版本用git或者复制文件夹都行。数学建模比赛期间大家精神高度紧张误删文件、改错数据是常有的事备份能救命。最后再分享一个小技巧论文里的所有结果表一定要在代码里直接生成Excel或者CSV文件然后从文件导入论文用的表格工具里不要手动从终端复制粘贴。手动复制容易出现精度丢失和格式错乱我们就在这方面吃过亏。备赛时间有限把流程尽量自动化留给检查和改论文的时间自然就多了。本文还有配套的精品资源点击获取