尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

2024国赛C题:农作物种植策略的整数规划建模与PuLP求解

2024国赛C题:农作物种植策略的整数规划建模与PuLP求解 简介一套面向2024年全国大学生数学建模竞赛C题「农作物的种植策略」的一等奖完整方案适合数学建模参赛者、指导老师及相关算法爱好者学习。该资源基于2023年数据围绕贪心算法与最优化模型在复杂约束下对种植方案进行深入求解并讨论了价格弹性、间作等关键因素。资源共296个文件压缩包12.5MB其中JSON文件231个用于数据存储与中间结果32个TXT文本用于说明或输出21个XLSX电子表格处理数据4个Python脚本实现算法与模型另有PDF论文、XML配置、Markdown笔记等目录结构清晰便于按模块查阅。已有228人学习适合想快速掌握解题思路、代码实现与论文写作的参赛者。解压后可见完整的数据、源码与论文可直接复现一等奖求解流程并附有算法注释和项目配置能帮助读者理解贪心策略、优先队列在种植优化中的应用缩短备赛时间。1. 2024国赛C题考的其实不是“种什么赚钱”2024国赛C题表面叫“农作物的种植策略”本质是带政策性硬约束的多年期整数规划几十个地块、十几类作物、七年窗口叠加重茬限制和政策面积下限。最反直觉的一点是——不考虑轮作时算出的最优解加入连作限制后可能连可行解都算不上。这道题对从业者的价值是把农业排产最常见的约束封装进一个可求解模型地块不超种、豆科不重茬、口粮面积有下限、价格随年份波动。能把这组约束正确翻译成求解器语言比堆一个复杂算法重要得多。适合备赛队伍也适合做种植规划与智慧农业排产模块的工程师。下面按数据清洗—整数规划建模—多年期扩展—论文产出的顺序用 Python PuLP 完整走一遍代码不依赖商业求解器本地装好 pandas、numpy、pulp 就能复现。2. 数据层把“地块-作物-年度”整理成规划能吃的结构常见做法是先把附件拆成五张底表再用唯一键对齐。C题原始数据分散在不同 sheet 里字段命名也不统一直接拿原始列名建数学记号后面写约束时容易来回返工。先把数据层整理干净建模阶段才能把精力放在约束表达上。2.1 从原始表拆出五张底表先定唯一键拿到附件后不要试图把所有列都塞进模型。按下面五张表拆每个表都保留一个联合唯一键底表必须字段用途唯一键地块表地块编号、面积亩面积约束、是否休耕地块编号作物表作物编号、作物类别、是否政策性作物约束的作物分组作物编号产量表地块编号、作物编号、年份、亩产目标函数收益计算地块作物年份成本表地块编号、作物编号、年份、亩均成本目标函数成本地块作物年份价格表作物编号、年份、单价目标函数收入作物年份“地块作物年份”是整个模型的主键。后续所有参数字典都用(i, t, c)三元组做键建模循环里直接按这个索引取数能省掉大量字段对齐的麻烦。2.2 Python数据清洗与字段对齐把附件读进来之后第一步是统一单位、清理缺失值、检查负值。下面这段示例代码处理地块信息表其他表按同样方式处理import pandas as pd # 用 GBK 编码读取国赛附件里的中文表头 plot_df pd.read_csv(地块信息.csv, encodinggbk) # 面积统一转成数值非数字置为 NaN plot_df[面积] pd.to_numeric(plot_df[面积], errorscoerce) # 删除面积缺失或不合理的地块 plot_df plot_df[plot_df[面积] 0] # 缺失率超过 50% 的列对建模没有帮助直接丢弃 missing_rate plot_df.isna().mean() drop_cols missing_rate[missing_rate 0.5].index.tolist() plot_df plot_df.drop(columnsdrop_cols) # 打印最终列名方便后面按列名建立字典索引 print(plot_df.columns.tolist())encodinggbk是处理中文表头的常见参数Excel 另存的 CSV 经常是 GBK 编码不加会直接抛 UnicodeDecodeError。pd.to_numeric的errorscoerce会把“1,200 亩”这类带逗号的脏数据转成 NaN再通过dropna或条件筛选过滤掉。缺失率 0.5 的阈值是经验值保留一半以上有效数据的列才对模型有意义如果面积列缺失过多直接删掉这块地比猜一个值更稳妥。2.3 参数表设计与负值检查清洗完成后把所有参数整理成统一的字典结构推荐用(i, t, c)元组做键。核心参数表设计如下参数名代码符号单位来源亩产yield_[(i, t, c)]公斤/亩产量表单价price[t][c]元/公斤价格表成本cost[(i, t, c)]元/亩成本表补贴subsidy[(i, t, c)]元/亩补贴规则文件地块面积area[i]亩地块表这里有一个容易踩的坑补贴不要和价格合并成“净价”。补贴往往只针对特定作物、特定年份比如大豆生产者补贴合并后你没法单独分析政策约束对结果的影响。负值检查也要单独做——成本出现负数说明可能把补贴混进了成本列价格出现负数直接剔除该记录。这些检查做完才能进入建模阶段。3. 建模层用整数规划把C题的约束翻成可求解目标数据就绪后建模的核心是选择决策变量。此题常见做法是离散决策一个地块、一个年度、只种一种作物。这样做的好处是重茬、轮作这类约束能直接用二元变量表达省掉大面积线性化。3.1 决策变量与目标函数怎么写不翻车决策变量定义为x[i][t][c]取值 0 或 1表示地块i在第t年是否种植作物c。这是本题最常用的选择因为题目里地块边界固定、面积已知离散决策不会带来明显精度损失。如果强行用连续面积变量面积切割会引入大量非线性限制求解规模和调试成本都会上升。目标函数是七年总利润最大化利润由三部分构成销售收益亩产 × 单价 × 种植面积补贴单位面积补贴 × 种植面积只对指定作物生效种植成本单位面积成本 × 种植面积用 PuLP 写出来是这样import pulp prob pulp.LpProblem(CropPlanning, pulp.LpMaximize) # 目标七年总利润 销售收益 补贴 - 种植成本 prob pulp.lpSum( x[i][t][c] * area[i] * ( yield_[(i, t, c)] * price[t][c] subsidy.get((i, t, c), 0) - cost[(i, t, c)] ) for i in plots for t in years for c in crops )subsidy.get((i, t, c), 0)比直接subsidy[(i, t, c)]安全少了补贴的作物自动补零不需要手工给每个作物建补贴条目。目标函数里每一项的意思x[i][t][c]决策这块地种不种area[i]把二元决策换算成实际面积括号里是单位面积净收益。3.2 重茬限制、面积平衡与政策性下限的数学化三条约束是这道题的骨架缺一条求解结果就不能落地约束1每地块每年至多一类作物: sum_c x[i][t][c] 1 约束2大豆不可连作: x[i][t][大豆] x[i][t1][大豆] 1 约束3年度大豆面积下限: sum_i x[i][t][大豆] * area[i] 50约束1表示地块 i 在第 t 年要么种一种作物要么休耕但不能同时种两种。约束2是重茬限制的典型写法把相邻两年的二元变量相加限制在 1 以内等价于“不能连续两年种大豆”。如果要限制“三年内只能种一次”把连续三年的变量相加并限制小于等于 1 即可。约束3是政策性面积下限——题目或当地政策要求豆类种植面积不低于某个数值乘上area[i]才是真实亩数。三条约束写完后模型已经具备可行域。此时再用求解器求解得到的结果才是政策上可解释、农业上可执行的方案。3.3 PuLP求解七年种植日历的最小可运行代码下面是一份可直接运行的最小示例代码用三块地、三种作物、七年窗口演示完整建模流程。用模拟数据填充替换成第 2 节整理好的真实字典即可使用import pulp import numpy as np plots [地块A, 地块B, 地块C] crops [小麦, 玉米, 大豆] years list(range(2024, 2031)) area {地块A: 120, 地块B: 200, 地块C: 160} # 用固定随机种子生成模拟数据可复现 rng np.random.default_rng(2024) price {t: {小麦: 2.4, 玉米: 2.2, 大豆: 5.0} for t in years} cost {(i, t, c): rng.uniform(350, 800) for i in plots for t in years for c in crops} yield_ {(i, t, c): rng.uniform(280, 650) for i in plots for t in years for c in crops} subsidy {(i, t, 大豆): 150 for i in plots for t in years} prob pulp.LpProblem(CropRotation, pulp.LpMaximize) # 决策变量地块i第t年是否种c x pulp.LpVariable.dicts(x, (plots, years, crops), catBinary) # 目标七年总利润最大化 prob pulp.lpSum( x[i][t][c] * area[i] * ( yield_[(i, t, c)] * price[t][c] subsidy.get((i, t, c), 0) - cost[(i, t, c)] ) for i in plots for t in years for c in crops ) # 约束1每块地每年最多一种作物 for i in plots: for t in years: prob pulp.lpSum(x[i][t][c] for c in crops) 1 # 约束2大豆不能连作 for i in plots: for t in range(2024, 2030): prob x[i][t][大豆] x[i][t 1][大豆] 1 # 约束3政策要求每年大豆总面积不低于 50 亩 for t in years: prob pulp.lpSum(x[i][t][大豆] * area[i] for i in plots) 50 # 求解gapRel控制MIP求解精度 prob.solve(pulp.PULP_CBC_CMD(msgFalse, gapRel0.01)) print(f求解状态: {pulp.LpStatus[prob.status]}) print(f最优利润: {pulp.value(prob.objective):,.2f} 元) # 输出七年种植日历 for i in plots: row [] for t in years: chosen [c for c in crops if pulp.value(x[i][t][c]) 0.5] row.append(f{t}: {chosen[0] if chosen else 休耕}) print(i, | .join(row))catBinary把变量声明为 0-1 整数变量PULP_CBC_CMD(msgFalse, gapRel0.01)里的gapRel是求解器停止条件表示当整数解与线性松弛上界的相对间隙小于 1% 时停止。CBC 默认 gap 是 0追求完全最优会让大模型跑很久竞赛场景下 1%~5% 的 gap 完全够用。输出种植日历那段代码先判断该年有没有选中作物再决定填作物名还是“休耕”避免next()在空集上抛异常。4. 多年期扩展轮作模板与价格情景的进阶处理第 3 章的最小模型能跑通但离一等奖还差两步一是地块扩大到几十块后求解变慢怎么处理二是把价格波动和轮作制度写得更贴近真实农业政策。这两点分开处理。4.1 模型变大后求解器参数与性能控制地块数量从 3 涨到 30变量规模从每个地块 21 个变量涨到 210 个如果再把作物细分二元变量会超过 2000 个。这个规模对 CBC 不构成压力真正拖慢求解的是约束写得不够紧。先给一组工程上常用的求解器参数参数建议取值作用gapRel0.01 ~ 0.05提前终止不追求严格最优timeLimit300秒硬上限避免求解器跑飞threads4多线程加速 CBCmsgFalse关闭求解日志只看最终结果在 PuLP 里可以这样组合传参prob.solve(pulp.PULP_CBC_CMD(msgFalse, gapRel0.02, timeLimit300, threads4))gapRel0.02意味着当求解器找到一个可行解且该解的目标值与线性松弛上界之差在 2% 以内就认为求解完成。这不是偷懒而是 MIP 的工程惯例——最后那 1% 的优化空间往往需要 10 倍以上的求解时间在论文里写明“求解间隙控制在 2% 以内”比把求解器干等一小时更专业。如果换用 Gurobi把 solver 换成GUROBI_CMD并保持相同的gapRel、timeLimit参数即可模型本身不需要改。4.2 轮作约束的两种实现禁止式与模板式第 3 章的大豆连作约束属于“禁止式”只限制相邻两年不种同一种作物之后的年份随意安排。这种方法简单适合蔬菜这类重茬敏感但轮作规律不强的作物。对小麦—玉米—大豆这类有固定轮作节奏的主粮更合适的是“模板式”轮作。模板式约束的思路是给每个地块定义一个轮作周期例如cycle [小麦, 玉米, 大豆]地块每年只能处于其中一个位置。先定义z[i][t][k]表示地块 i 第 t 年处于轮作位置 k然后建立三组关系# 示例模板轮作cycle [小麦, 玉米, 大豆] # 1. 每年必须处于一个轮作位置 # sum_k z[i][t][k] 1 # 2. 轮作位置对应到实际作物 # x[i][t][c] sum_{k: cycle[k] c} z[i][t][k] # 3. 下一年位置自动后移一位 # z[i][t1][(k1) % 3] z[i][t][k]第三条是核心它强制地块按“小麦→玉米→大豆→小麦”的顺序循环不允许跳位。实际建模时这三个公式用 PuLP 写成lpSum约束即可。模板式比禁止式紧凑得多同样约束小麦不连作禁止式只能保证今年种了明年不种但模板式还能保证后年一定回到小麦这对政策中“鼓励轮作”的条款是硬支撑。地块差异大时可以按地块分组主粮地块用模板式经济作物地块用禁止式。4.3 价格波动下的多情景期望模型价格表里每年一个数字直接作为确定性参数会让最优方案对价格极端敏感。更稳的做法是给价格建三个情景——乐观、中性、悲观并给每个情景一个概率目标函数改为最大化期望利润。这种处理方法在 python 量化交易策略代码里很常见放在种植规划里同样成立。scenarios {low: 0.95, mid: 1.0, high: 1.05} prob_weight {low: 0.25, mid: 0.5, high: 0.25} # 决策变量不带情景下标因为种植决策在年初就要确定 profit_s {} for s, factor in scenarios.items(): profit_s[s] pulp.lpSum( x[i][t][c] * area[i] * ( yield_[(i, t, c)] * price[t][c] * factor subsidy.get((i, t, c), 0) - cost[(i, t, c)] ) for i in plots for t in years for c in crops ) prob pulp.lpSum(prob_weight[s] * profit_s[s] for s in scenarios)需要注意决策变量x没有加情景下标因为种植计划必须在年初定下来不能等价格落地再调整。用概率加权目标函数求解后得到的是一套对三种情景都相对平均的方案。如果不想让低价格情景下亏损太严重可以再追加一条约束profit_s[low] 0.9 * 确定性最优值表示在最差情景下利润不低于确定性最优解的 90%。这条约束会在风险与收益之间强制压出一个平衡点也是论文里能写进“模型的稳健性分析”一节的实打实内容。5. 论文图表与敏感性分析把求解结果变成评委记住的理由5.1 必画的三张图以及对应的绘图代码一等奖论文的图表不需要花哨但要能直接说明约束在起作用。按经验三张图最有效七年的作物面积堆叠图展示政策面积下限和轮作约束下各类作物种植面积变化地块-年度种植日历热力图行是地块列是年份色块是作物评委一眼看出轮作节奏逐年利润构成图拆出销售收益、补贴、成本三条线或堆叠柱。堆叠面积图是最容易快速出效果的一张核心代码如下import matplotlib.pyplot as plt # 从求解结果统计每年每种作物面积 year_area {t: {c: 0.0 for c in crops} for t in years} for i in plots: for t in years: for c in crops: if pulp.value(x[i][t][c]) 0.5: year_area[t][c] area[i] # 画堆叠面积图 fig, ax plt.subplots(figsize(9, 5)) x_labels [str(t) for t in years] bottom [0] * len(years) for c in crops: vals [year_area[t][c] for t in years] ax.bar(x_labels, vals, bottombottom, labelc) bottom [b v for b, v in zip(bottom, vals)] ax.set_ylabel(种植面积亩) ax.legend() plt.savefig(crop_area_stack.png, dpi300, bbox_inchestight)dpi300是论文插图基本要求bbox_inchestight避免坐标轴标签被裁掉。这张图重点展示约束3是否被激活如果大豆面积线长期贴在政策下限 50 亩上说明该约束是模型的关键瓶颈正文讨论时可以直接引用。5.2 用扰动法做敏感性分析而不是空谈“模型稳健”MILP 求解完成后输出的对偶变量和影子价格不能直接解释因为整数规划的对偶信息在分支定界后被破坏了。所以论文里做敏感性分析最稳妥的方式是扰动法把关键参数上下浮动一定幅度重新求解模型记录目标值变化。下面这种表可以直接塞进论文情景参数改动最优总利润元相对变化基准无4,521,680—大豆面积下限 -10%50 → 45 亩4,558,2000.81%大豆面积下限 10%50 → 55 亩4,489,300-0.72%大豆价格 -10%5.0 → 4.5 元/kg4,410,050-2.47%大豆价格 10%5.0 → 5.5 元/kg4,630,1002.40%具体操作是把 3.3 节的模型包成一个函数参数作为入参循环调用solve_crop_model(soybean_min_area45)这类接口。扰动幅度一般取 ±5% 或 ±10%太小说明不了问题太大会跑出政策约束的可行域。把这张表放到论文里比写三段“模型具有很好的稳健性”的说服力强得多。最后还能从表里反推出约束的稀缺性哪一行的利润变化最大哪一个约束就是这个模型的瓶颈评委的问题也大多集中在这张表上。本文还有配套的精品资源点击获取
返回列表