前言:看不懂问AI,要注意本文采用的PuLP库是4.0+的最新版,其语法相比旧版本有一些不同,如果在网络上或者AI给出了旧版本的程序,请自行把该库降级运行。
数学建模竞赛中的问题通常可分为预测类与决策类。决策类问题的本质多为优化问题,即在有限资源与技术限制下,通过合理配置决策变量,使某一效益指标达到最大或成本达到最小。以2024年全国大学生数学建模竞赛C题“农作物种植策略”为例,41种作物需分配至34块土地与20个大棚中,需同时满足土地类型限制、轮作禁忌、产量与销售波动、作物替代互补等多重约束,最终目标为实现总收入最大化。此类问题均可抽象为标准的数学规划模型。在实际科研与工业场景中,Python凭借其开源生态、强大的数据处理能力以及与机器学习管线的无缝衔接,已成为优化建模的主流工具。
一、 规划模型的核心概念与数学表达
数学规划的一般形式
设决策变量为 x=(x_1,x_2,…,x_n )^⊤,目标函数为 f(x),约束条件由等式 h_i (x)=0与不等式 g_j (x)≤0(或 ≥0)构成,可行域为 Ω。规划模型的通用数学表达为:

其中“s.t.”为“subject to”的缩写,表示“受约束于”。
规划模型的分类
根据模型特征,规划问题可进行如下分类:
按决策变量取值:实数规划、整数规划、0-1规划
按目标函数数量:单目标规划、多目标规划
按函数性质:线性规划、非线性规划、二次规划
按约束存在性:有约束问题、无约束问题
建模三步骤
建立规划模型遵循固定逻辑:
确定决策变量:明确问题中可控制、待优化的未知量。
构建目标函数:将优化目标(利润最大、成本最小、时间最短等)表示为决策变量的函数。
列出约束条件:将资源限制、技术要求、物理规律等转化为等式或不等式。
二、 线性规划建模:以奶制品生产计划为例
当目标函数与所有约束条件均为决策变量的一次函数时,该模型称为线性规划模型。接下来以“加工奶制品的生产计划”为例演示建模过程。
某加工厂每日获得50桶牛奶供应,正式工人劳动时间为480小时。1桶牛奶可在甲类设备加工12小时生产3kg产品A1,或在乙类设备加工8小时生产4kg产品A2。A1利润为24元/kg,A2利润为16元/kg。甲类设备每日最多加工100kg A1,乙类设备无产能限制。求使每日总利润最大的生产计划。
线性规划模型构建
设 x_1为用于生产A1的牛奶桶数,x_2 为用于生产A2的牛奶桶数。
目标函数:总利润 z=24×3x_1+16×4x_2=72x_1+64x_2
约束条件:
原料约束:x_1+x_2≤50
工时约束:12x_1+8x_2≤480
设备产能约束:3x_1≤100
非负约束:x_1,x_2≥0
综合可得线性规划模型:

Python求解代码实现
PuLP是Python中开源的线性/整数规划建模库,语法贴近数学表达,底层默认调用CBC求解器。要使用PuLP,需要先安装该库,一定要下载尽可能新的版本,本文采用的是4.0.0a3版本。
完整代码如下:
import pulp# 1. 创建优化问题对象prob = pulp.LpProblem("Milk_Production", pulp.LpMaximize)# 2. 定义决策变量x1 = prob.add_variable("x1", lowBound=0, cat="Continuous") x2 = prob.add_variable("x2", lowBound=0, cat="Continuous")# 3. 添加目标函数prob += 72 * x1 + 64 * x2, "Total_Profit"# 4. 添加约束条件prob += x1 + x2 <= 50, "Milk_Supply"prob += 12 * x1 + 8 * x2 <= 480, "Labor_Hours"prob += 3 * x1 <= 100, "Machine_A_Capacity"# 5. 调用求解器prob.solve()# 6. 输出求解结果print("求解状态:", pulp.LpStatus[prob.status])print("最优决策: x1 = {:.2f}, x2 = {:.2f}".format(pulp.value(x1), pulp.value(x2)))print("最大利润: {:.2f} 元".format(pulp.value(prob.objective))) |
接下来进行逐行逻辑拆解:
import pulp
目的是引入PuLP优化库,PuLP是Python中最流行的线性规划库,用于建立和求解优化问题。导入后才能使用其所有功能
prob = pulp.LpProblem("Milk_Production", pulp.LpMaximize)
目的是创建优化问题对象并指定求解类型,第一个参数是问题名称(可自定义),第二个参数pulp.LpMaximize表示求最大值(若求最小值用pulp.LpMinimize)。这是建模的起点,定义了整个优化问题的框架
x1 = prob.add_variable("x1", lowBound=0, cat="Continuous")
目的是定义决策变量x1并设置约束条件,决策变量是模型中需要求解的未知量。lowBound=0表示x1≥0(非负约束),cat=""Continuous""表示x1是连续变量(可取小数)。在PuLP 4.0+版本中必须通过prob.add_variable()方法创建
x2 = prob.add_variable("x2", lowBound=0, cat="Continuous")
目的是定义决策变量x2并设置约束条件,同x1,定义第二个决策变量。两个变量通常代表不同产品的产量、资源分配量等。非负约束是线性规划的标准要求,因为实际问题中产量不能为负
prob += 72 * x1 + 64 * x2, "Total_Profit"
目的是添加目标函数:最大化总利润,这是优化问题的核心目标。72和64分别是x1和x2的单位利润系数。逗号后面的字符串是目标函数的名称(便于调试)。prob += 表示向问题中添加表达式
prob += x1 + x2 <= 50, "Milk_Supply"
目的是添加约束条件1:原料供应约束,表示x1和x2的总消耗不能超过50单位原料。<= 50是资源上限,""Milk_Supply""是约束名称。实际问题中可能是牛奶、原材料等的供应量限制
prob += 12 * x1 + 8 * x2 <= 480, "Labor_Hours"
目的是添加约束条件2:工时约束,表示生产x1需要12小时/单位,x2需要8小时/单位,总工时不超过480小时。这类约束反映人力资源、机器时间等限制
prob += 3 * x1 <= 100, "Machine_A_Capacity"
目的是添加约束条件3:设备产能约束,表示x1的生产受设备A限制,每单位消耗3小时产能,总产能100小时。实际问题中可能是特定设备、场地的专属限制
prob.solve()
目的是调用求解器求解优化模型,自动选择可用求解器(如CBC、GLPK等)进行计算。这一步执行所有数学运算,寻找满足所有约束条件下使目标函数最优的解
print("求解状态:", pulp.LpStatus[prob.status])
目的是输出求解状态信息,prob.status返回求解结果代码,pulp.LpStatus将其转换为可读文本。常见状态:Optimal(最优解)、Infeasible(无解)、Unbounded(无界)。用于验证模型是否有有效解
print("最优决策: x1 = {:.2f}, x2 = {:.2f}".format(pulp.value(x1), pulp.value(x2)))
目的是输出最优决策变量的值,pulp.value(x1)提取变量x1的最优解。{:.2f}表示保留2位小数。这些值就是使利润最大化的最佳生产方案
print("最大利润: {:.2f} 元".format(pulp.value(prob.objective)))
目的是输出最优目标函数值,prob.objective是目标函数对象,pulp.value()计算其在最优解处的值。这就是在最优决策下能获得的最大利润
根据该代码的运行逻辑,其输出结果应该是:
求解状态: Optimal
最优决策: x1 = 20.00, x2 = 30.00
最大利润: 3360.00 元
因此对于这个牛奶生产问题,其解应该是:
20桶牛奶生产A1,30桶生产A2,利润3360元。
三、 整数规划与0-1规划:以混合泳接力队选拔为例
当决策变量必须取整数或仅限0/1时,需使用整数规划或0-1规划。本文以“混合泳接力队选拔”为例:在一次4×100混合泳接力赛中,甲、乙、丙、丁、戊五人的百米成绩如下表所示:
| 甲 | 乙 | 丙 | 丁 | 戊 |
蝶泳 | 1'06"8 | 57"2 | 1'18" | 1'10" | 1'07"4 |
仰泳 | 1'15"6 | 1'06" | 1'07"8 | 1'14"2 | 1'11" |
蛙泳 | 1'27" | 1'06"4 | 1'24"6 | 1'09"6 | 1'23"8 |
自由泳 | 58"6 | 53" | 59"4 | 57"2 | 1'02"4 |
如何选拔队员组成4×100米混合泳接力队?
0-1规划数学模型构建
设 x_ij∈{0,1}表示队员 i是否参加泳姿 j,c_ij 为对应成绩(秒):

Python求解实现
import pulpimport numpy as np# 成绩矩阵(单位:秒),行对应队员,列对应泳姿[蝶,仰,蛙,自]c = np.array([[66.8, 75.6, 87.0, 58.6],[57.2, 66.0, 66.4, 53.0],[78.0, 67.8, 84.6, 59.4],[70.0, 74.2, 69.6, 57.2],[67.4, 71.0, 83.8, 62.4] ]) prob = pulp.LpProblem("Relay_Selection", pulp.LpMinimize)# 定义0-1决策变量矩阵x = {}for i in range(5):for j in range(4):x[(i, j)] = prob.add_variable(f"x_{i}_{j}", lowBound=0, upBound=1, cat='Integer')# 目标函数prob += pulp.lpSum(c[i][j] * x[(i, j)] for i in range(5) for j in range(4))# 约束1:每人最多参加一项for i in range(5):prob += pulp.lpSum(x[(i, j)] for j in range(4)) <= 1# 约束2:每项泳姿必须有且仅有1人for j in range(4):prob += pulp.lpSum(x[(i, j)] for i in range(5)) == 1prob.solve()# 输出选拔方案print("选拔方案(1表示入选,0表示未入选):")for i in range(5):row = [int(pulp.value(x[(i, j)])) for j in range(4)]print(f"队员{i+1}: {row}")print(f"接力队最短总用时: {pulp.value(prob.objective):.2f} 秒") |
接下来进行逐行逻辑拆解(前面讲过的不讲了):
import numpy as np
目的是引入NumPy数值计算库,用于处理矩阵和数组运算。这里用来存储成绩矩阵,比Python原生列表更高效
c = np.array([[66.8, 75.6, 87.0, 58.6],[57.2, 66.0, 66.4, 53.0],[78.0, 67.8, 84.6, 59.4],[70.0, 74.2, 69.6, 57.2],[67.4, 71.0, 83.8, 62.4]])
目的是创建成绩矩阵(5个队员×4种泳姿),二维数组存储每个队员在各泳姿的成绩。np.array将嵌套列表转换为NumPy数组,支持向量化运算
prob = pulp.LpProblem("Relay_Selection", pulp.LpMinimize)
目的是创建最小化优化问题,pulp.LpMinimize表示求最小值(与线性规划的最大化不同)。接力问题目标是总用时最短
x = {}for i in range(5):for j in range(4):x[(i, j)] = prob.add_variable(f"x_{i}_{j}", lowBound=0, upBound=1, cat='Integer')
目的是定义0-1整数决策变量字典,使用字典存储20个变量(5×4)。lowBound=0,upBound=1,cat='Integer'组合实现0-1变量(只能取0或1),表示"选中/不选中"
prob += pulp.lpSum(c[i][j] * x[(i, j)] for i in range(5) for j in range(4))
目的是添加目标函数:最小化总用时,pulp.lpSum()是PuLP的求和函数,用于高效构建线性表达式。计算所有被选中队员的成绩总和
for i in range(5):prob += pulp.lpSum(x[(i, j)] for j in range(4)) <= 1
目的是添加约束条件组1:每人最多参加一个项目,每个队员的4个变量之和≤1,保证一人最多选一种泳姿。这是指派问题的典型约束
for j in range(4):prob += pulp.lpSum(x[(i, j)] for i in range(5)) == 1
目的是添加约束条件组2:每种泳姿必须恰好1人参加,每种泳姿的5个变量之和=1,保证每种泳姿有且只有1人。等号约束比不等号更严格
row = [int(pulp.value(x[(i, j)])) for j in range(4)]
目的是将最优解转换为0-1整数显示,pulp.value()返回浮点数(如0.999999),int()转换为整数0或1,便于阅读结果
根据该代码的运行逻辑,其输出结果应该是:
选拔方案(1表示入选,0表示未入选):
队员1: [0, 0, 0, 1]
队员2: [1, 0, 0, 0]
队员3: [0, 1, 0, 0]
队员4: [0, 0, 1, 0]
队员5: [0, 0, 0, 0]
接力队最短总用时: 253.20 秒
因此对于这个混合泳接力队选拔问题,其解应该是:
甲报名自由泳,乙报名蝶泳,丙报名仰泳,丁报名蛙泳,戊不报名。
四、 常见的规划模型
问题1:运输问题
设某种物资共有 m个产地 A_1,A_2,…,A_m,各产地的产量分别是 a_1,a_2,…,a_m;有 n个销地 B_1,B_2,…,B_n,各销地的销量分别为 b_1,b_2,…,b_n。假定从产地 A_i (i=1,2,…,m)向销地 B_j (j=1,2,…,n)运输单位物资的运价是 c_ij,问怎样调运才能使总运费最小?
产销平衡问题

- 产销不平衡问题
- 供大于求

供不应求

问题2:生产组织与计划问题
工厂用 m种设备 A_1,A_2,…,A_m生产 n种产品 B_1,B_2,…,B_n。在一个生产周期内,已知第 i台设备 A_i只能工作 a_i个机时。工厂必须完成产品 B_j至少 b_j件,设备 A_i生产产品 B_j所需要的机时和成本分别为 t_ij,c_ij。试建立相应的数学模型,使设备能在计划周期内完成计划但又使成本达到最低。

问题3:工厂选址问题
设有 n个需求点(城市、仓库、商店等),有 m个可供选择的建厂地址,每个地址最多可建一个工厂。在 i地址建立工厂的生产能力为 D_i,在 i地址经营工厂的单位时间固定成本为 a_i,需求点 j的需求量为 b_j,从厂址 i到需求点 j的单位运费为 c_ij。问应如何选择厂址和安排运输计划,使相应的成本最小。

其中 y_i为0-1决策变量:

此类模型称为混合整数线性规划。
问题4:设备购置和安装问题
工厂需要 m种设备 A_1,A_2,…,A_m,设备 A_i的单价为 p_i,工厂已有第 i种设备 a_i台。现有资金 M元可用于购置这些设备。该厂有 n处可安装这些设备,B_j 处最多可安装 b_j台。将一台设备 A_i安装在 B_j处的经济效益为 c_ij元。问应如何购置和安装这些设备,才能使总的经济效益最高。

问题5:货郎问题(旅行商问题,TSP)
货郎要到 n个地方去卖货。已知两个地方 A_i和 A_j之间的距离为 d_ij。如何选择一条道路,使得货郎每个地方走一遍后回到起点,且所走的路径最短。
定义 0-1变量:

则相应的模型为(其中 ∣S∣表示集合 S中元素的总数):
