基于线性规划的“广义指派问题”建模、分解与列生成算法求解示例
字数 4586 2025-12-23 14:48:00

基于线性规划的“广义指派问题”建模、分解与列生成算法求解示例

我将为您讲解一个尚未出现在列表中的线性规划算法题目。这次我们聚焦于广义指派问题,并介绍如何利用线性规划的Dantzig-Wolfe分解列生成算法来高效求解大规模实例。与基本的指派问题(如一人一任务)不同,广义指派问题允许一个任务被多个工人(或机器)以不同的效率和成本部分完成,但每个工人的总工作量有限制,且每个任务必须被完全完成。这在实际的资源分配、生产调度中非常常见。

题目描述

假设我们有一组工人(或机器)集合 I = {1, 2, ..., m} 和一组任务集合 J = {1, 2, ..., n}。每个工人 i 拥有一个工作能力(如时间、产能)上限 b_i。将任务 j 分配给工人 i 来处理,会产生一个成本 c_{ij}(例如耗时、费用),并且会消耗工人 i 的 a_{ij} 单位能力。我们的目标是:为每一个任务 j 选择一个或多个工人来共同完成(即允许“拆分”任务),使得总成本最小,同时满足:

  1. 每个任务 j 必须被完全处理(需求量为1个单位)。
  2. 每个工人 i 处理所分配任务的总资源消耗不超过其能力上限 b_i。
  3. 任务 j 分配给工人 i 的比例(或数量)x_{ij} 是非负的。

这是一个经典的、可以建模为线性规划的问题,但直接求解大规模问题(m, n 很大)可能效率不高。列生成算法通过将问题分解,迭代地只生成“有潜力”的决策变量(列)来构建和求解一个简化模型,从而高效处理大规模问题。


解题过程循序渐进讲解

步骤1:建立标准线性规划模型

我们先建立问题的紧凑线性规划模型,称为紧凑公式

  • 决策变量:令 x_{ij} ≥ 0 表示任务 j 分配给工人 i 的比例(或数量)。由于每个任务的总需求为1,这里的 x_{ij} 可以理解为工人 i 承担任务 j 的比例。
  • 目标函数:最小化总成本。Minimize: Σ_{i∈I} Σ_{j∈J} c_{ij} * x_{ij}
  • 约束条件
    1. 任务覆盖约束:每个任务必须被完全分配。对于每个任务 j ∈ J: Σ_{i∈I} x_{ij} = 1
    2. 工人能力约束:每个工人的资源消耗不能超过上限。对于每个工人 i ∈ I: Σ_{j∈J} a_{ij} * x_{ij} ≤ b_i
    3. 非负约束:x_{ij} ≥ 0,对所有 i, j。

这个模型是一个线性规划,可以用单纯形法等直接求解。但当 m 和 n 都很大时,变量和约束的数量(m*n + m + n)会变得非常庞大,直接求解计算负担重。

步骤2:Dantzig-Wolfe分解——识别可分解结构

观察上述模型,约束可以被分成两组:

  • 耦合约束:任务覆盖约束(Σ_{i∈I} x_{ij} = 1)将所有工人联系在了一起,是“耦合”的。
  • 独立约束:工人能力约束(Σ_{j∈J} a_{ij} * x_{ij} ≤ b_i)是按工人分离的。每个工人的约束只涉及分配给该工人的变量 {x_{ij} | j ∈ J}。

这种结构是Dantzig-Wolfe分解的完美应用场景。我们可以将原问题重新表述。

核心思想:对于每个工人 i,考虑其所有可行的工作安排。一个工作安排是指为该工人 i 分配一组任务及其处理量 (x_{i1}, x_{i2}, ..., x_{in}),满足其自身的能力约束 Σ_j a_{ij} x_{ij} ≤ b_i 和 x_{ij} ≥ 0。这样的一个可行安排称为一个“模式”或“列”。

由于每个工人的可行安排集合(称为多面体 P_i)可能包含无穷多个点(因为 x_{ij} 是连续的),我们无法枚举所有模式。Dantzig-Wolfe分解告诉我们,原问题的最优解可以表示为各个工人可行多面体 P_i 中极点的凸组合。

步骤3:构建主问题(Master Problem, MP)和定价子问题(Pricing Subproblem, PSP)

基于分解思想,我们构建一个等价的新模型:

  • 主问题(MP)决策变量:假设我们(暂时)知道工人 i 的所有可行模式的集合 S_i。设 λ_{ik} 是一个连续变量,表示在最终解中,工人 i 采用其第 k 个模式的程度(比例)。注意,对于一个工人,我们可以组合使用多个模式。
  • 主问题(MP)模型
    • 设对于工人 i 的可行模式 k,其参数为:
      • c_{ik} = 该模式的成本 = Σ_j c_{ij} * x_{ij}^k
      • d_{jk}^i = 该模式处理任务 j 的量 = x_{ij}^k
    • 目标:Minimize Σ_{i∈I} Σ_{k∈S_i} c_{ik} * λ_{ik}
    • 约束
      1. 任务覆盖约束:对于每个任务 j,所有工人所有模式对其的处理量总和必须为1。即:Σ_{i∈I} Σ_{k∈S_i} d_{jk}^i * λ_{ik} = 1, ∀ j ∈ J。
      2. 凸组合约束:对于每个工人 i,其使用的所有模式的程度之和为1(这意味着工人 i 的工作安排是它所有可行模式的一个凸组合)。即:Σ_{k∈S_i} λ_{ik} = 1, ∀ i ∈ I。
      3. 非负约束:λ_{ik} ≥ 0。
  • 这个MP模型与原问题等价,但变量是 λ_{ik}。如果我们能枚举所有 S_i,MP 可以直接求解。但 S_i 是无穷集,我们无法全部列出。

列生成算法的智慧在于:我们不需要一开始就知道所有 S_i。我们从每个工人 i 的一个很小的、初始的可行模式集合 S_i'(可能只是一个简单模式,比如不处理任何任务,即零模式)开始,求解一个限制主问题

步骤4:初始限制主问题与对偶变量

  1. 初始化:为每个工人 i 构建一个或多个简单的可行模式,加入 S_i'。最常用的初始模式是“空模式”(所有 x_{ij}=0),其成本为0,不处理任何任务。这保证了限制主问题是可行的(虽然任务覆盖约束可能无法满足,可能需要引入人工变量或使用其他启发式方法生成更好的初始列,这里为简化,我们假设初始模式集合能使限制主问题可行)。
  2. 求解限制主问题:用线性规划求解器(如单纯形法)求解当前的限制主问题,得到当前最优解 λ* 和最重要的——对偶变量的值。
    • 设任务覆盖约束(Σ_i Σ_k d_{jk}^i * λ_{ik} = 1)对应的对偶变量为 π_j (每个任务 j 一个)。
    • 设凸组合约束(Σ_k λ_{ik} = 1)对应的对偶变量为 ω_i (每个工人 i 一个)。

步骤5:定价子问题——寻找“有负检验数”的新列

在单纯形法中,引入一个新变量(列)是否有利,取决于它的检验数(Reduced Cost)是否为负(对于最小化问题)。在我们的MP模型中,对于工人 i 的一个新模式(其决策变量为 λ_{i, new}),其检验数计算如下:

检验数 = c_{i, new} - Σ_j π_j * d_{j, new}^i - ω_i

其中,c_{i, new} = Σ_j c_{ij} * x_{ij},d_{j, new}^i = x_{ij}。所以,
检验数 = Σ_j c_{ij} * x_{ij} - Σ_j π_j * x_{ij} - ω_i = Σ_j (c_{ij} - π_j) * x_{ij} - ω_i

我们的目标是:对于每个工人 i,尝试找到一个可行的新模式(即一组满足工人 i 自身能力约束的 x_{ij} ≥ 0),使得其检验数为负。 如果找到了,将这个新模式作为一列加入限制主问题,因为它有可能改进当前解。

这引导出 m 个独立的定价子问题,每个对应一个工人 i:

定价子问题 i(对工人 i)
Minimize Σ_{j∈J} (c_{ij} - π_j) * x_{ij} - ω_i
Subject to:
Σ_{j∈J} a_{ij} * x_{ij} ≤ b_i
x_{ij} ≥ 0, ∀ j ∈ J

注意,ω_i 是一个常数。所以,子问题 i 等价于求解一个简单的线性规划(实际上是一个连续背包问题)
Minimize Σ_{j∈J} (c_{ij} - π_j) * x_{ij}
Subject to:
Σ_{j∈J} a_{ij} * x_{ij} ≤ b_i
x_{ij} ≥ 0, ∀ j ∈ J

步骤6:列生成迭代过程

  1. 求解当前限制主问题,得到对偶变量 π 和 ω。
  2. 遍历所有工人 i,求解对应的定价子问题 i。
  3. 检查终止条件:如果对于所有工人 i,其定价子问题的最优值(即最小化的 Σ_j (c_{ij} - π_j) * x_{ij})都大于等于 ω_i,那么意味着所有可能新列的检验数都 ≥ 0。根据线性规划的对偶理论,当前限制主问题的解就是原主问题(MP)的最优解,算法终止。
  4. 添加新列:如果存在某个工人 i’,其定价子问题的最优值 < ω_i’,那么我们就找到了一个检验数为负的新模式(由子问题的最优解 x_{ij}^* 定义)。将这个新模式添加到限制主问题中,对应工人 i’ 的新变量 λ_{i’, new}。其在该列中的系数为:
    • 在目标函数中的系数:c_{i’, new} = Σ_j c_{i’j} * x_{i’j}^*
    • 在任务覆盖约束 j 中的系数:d_{j, new}^{i’} = x_{i’j}^*
    • 在凸组合约束 i’ 中的系数:1
  5. 回到第1步,用新的列集合求解更新后的限制主问题。

步骤7:获取原问题解

当列生成算法终止时,我们得到了限制主问题(现在等价于完整MP)的最优解 λ*。我们需要将其“转换”回原问题的 x_{ij} 变量。

原问题的解为:对于每个工人 i 和任务 j,x*{ij} = Σ{k∈S_i} (x_{ij}^k * λ_{ik}^),其中 x_{ij}^k 是模式 k 中工人 i 处理任务 j 的量,λ_{ik}^ 是主问题中该模式的使用程度。

总结与核心思路

这个算法的美妙之处在于,它将一个大规模线性规划(变量数为 m*n)分解为:

  1. 一个“主协调”问题:负责确保所有任务被覆盖,并协调各工人的模式选择。其规模只与任务数 n 和当前生成的列数有关,通常远小于 m*n。
  2. m 个“局部”子问题:每个子问题只关心一个工人,结构简单(一个资源约束的线性规划),可以并行高效求解

通过迭代地在“主问题”和“子问题”之间交换对偶信息(π_j),算法能够“按需”生成有价值的决策模式,避免了对所有 m*n 种可能的分配组合进行显式枚举和存储,从而能够高效求解大规模的广义指派问题。这正是列生成和Dantzig-Wolfe分解在处理具有可分离结构的大规模线性规划时的威力所在。

基于线性规划的“广义指派问题”建模、分解与列生成算法求解示例 我将为您讲解一个尚未出现在列表中的线性规划算法题目。这次我们聚焦于 广义指派问题 ,并介绍如何利用线性规划的 Dantzig-Wolfe分解 和 列生成算法 来高效求解大规模实例。与基本的指派问题(如一人一任务)不同,广义指派问题允许一个任务被多个工人(或机器)以不同的效率和成本部分完成,但每个工人的总工作量有限制,且每个任务必须被完全完成。这在实际的资源分配、生产调度中非常常见。 题目描述 假设我们有一组工人(或机器)集合 I = {1, 2, ..., m} 和一组任务集合 J = {1, 2, ..., n}。每个工人 i 拥有一个工作能力(如时间、产能)上限 b_ i。将任务 j 分配给工人 i 来处理,会产生一个成本 c_ {ij}(例如耗时、费用),并且会消耗工人 i 的 a_ {ij} 单位能力。我们的目标是:为每一个任务 j 选择一个或多个工人来共同完成(即允许“拆分”任务),使得总成本最小,同时满足: 每个任务 j 必须被 完全 处理(需求量为1个单位)。 每个工人 i 处理所分配任务的总资源消耗不超过其能力上限 b_ i。 任务 j 分配给工人 i 的比例(或数量)x_ {ij} 是非负的。 这是一个经典的、可以建模为线性规划的问题,但直接求解大规模问题(m, n 很大)可能效率不高。列生成算法通过将问题分解,迭代地只生成“有潜力”的决策变量(列)来构建和求解一个简化模型,从而高效处理大规模问题。 解题过程循序渐进讲解 步骤1:建立标准线性规划模型 我们先建立问题的紧凑线性规划模型,称为 紧凑公式 。 决策变量 :令 x_ {ij} ≥ 0 表示任务 j 分配给工人 i 的比例(或数量)。由于每个任务的总需求为1,这里的 x_ {ij} 可以理解为工人 i 承担任务 j 的比例。 目标函数 :最小化总成本。Minimize: Σ_ {i∈I} Σ_ {j∈J} c_ {ij} * x_ {ij} 约束条件 : 任务覆盖约束 :每个任务必须被完全分配。对于每个任务 j ∈ J: Σ_ {i∈I} x_ {ij} = 1 工人能力约束 :每个工人的资源消耗不能超过上限。对于每个工人 i ∈ I: Σ_ {j∈J} a_ {ij} * x_ {ij} ≤ b_ i 非负约束 :x_ {ij} ≥ 0,对所有 i, j。 这个模型是一个线性规划,可以用单纯形法等直接求解。但当 m 和 n 都很大时,变量和约束的数量(m* n + m + n)会变得非常庞大,直接求解计算负担重。 步骤2:Dantzig-Wolfe分解——识别可分解结构 观察上述模型,约束可以被分成两组: 耦合约束 :任务覆盖约束(Σ_ {i∈I} x_ {ij} = 1)将 所有工人 联系在了一起,是“耦合”的。 独立约束 :工人能力约束(Σ_ {j∈J} a_ {ij} * x_ {ij} ≤ b_ i)是 按工人分离 的。每个工人的约束只涉及分配给该工人的变量 {x_ {ij} | j ∈ J}。 这种结构是Dantzig-Wolfe分解的完美应用场景。我们可以将原问题重新表述。 核心思想 :对于每个工人 i,考虑其 所有可行的工作安排 。一个工作安排是指为该工人 i 分配一组任务及其处理量 (x_ {i1}, x_ {i2}, ..., x_ {in}),满足其自身的能力约束 Σ_ j a_ {ij} x_ {ij} ≤ b_ i 和 x_ {ij} ≥ 0。这样的一个可行安排称为一个“模式”或“列”。 由于每个工人的可行安排集合(称为 多面体 P_ i)可能包含无穷多个点(因为 x_ {ij} 是连续的),我们无法枚举所有模式。Dantzig-Wolfe分解告诉我们,原问题的最优解可以表示为各个工人可行多面体 P_ i 中 极点 的凸组合。 步骤3:构建主问题(Master Problem, MP)和定价子问题(Pricing Subproblem, PSP) 基于分解思想,我们构建一个等价的新模型: 主问题(MP)决策变量 :假设我们(暂时)知道工人 i 的所有可行模式的集合 S_ i。设 λ_ {ik} 是一个连续变量,表示在最终解中,工人 i 采用其第 k 个模式的程度(比例)。注意,对于一个工人,我们可以组合使用多个模式。 主问题(MP)模型 : 设对于工人 i 的可行模式 k,其参数为: c_ {ik} = 该模式的成本 = Σ_ j c_ {ij} * x_ {ij}^k d_ {jk}^i = 该模式处理任务 j 的量 = x_ {ij}^k 目标 :Minimize Σ_ {i∈I} Σ_ {k∈S_ i} c_ {ik} * λ_ {ik} 约束 : 任务覆盖约束 :对于每个任务 j,所有工人所有模式对其的处理量总和必须为1。即:Σ_ {i∈I} Σ_ {k∈S_ i} d_ {jk}^i * λ_ {ik} = 1, ∀ j ∈ J。 凸组合约束 :对于每个工人 i,其使用的所有模式的程度之和为1(这意味着工人 i 的工作安排是它所有可行模式的一个凸组合)。即:Σ_ {k∈S_ i} λ_ {ik} = 1, ∀ i ∈ I。 非负约束 :λ_ {ik} ≥ 0。 这个MP模型与原问题等价,但变量是 λ_ {ik}。如果我们能枚举所有 S_ i,MP 可以直接求解。但 S_ i 是无穷集,我们无法全部列出。 列生成算法 的智慧在于: 我们不需要一开始就知道所有 S_ i 。我们从每个工人 i 的一个很小的、初始的可行模式集合 S_ i'(可能只是一个简单模式,比如不处理任何任务,即零模式)开始,求解一个 限制主问题 。 步骤4:初始限制主问题与对偶变量 初始化 :为每个工人 i 构建一个或多个简单的可行模式,加入 S_ i'。最常用的初始模式是“空模式”(所有 x_ {ij}=0),其成本为0,不处理任何任务。这保证了限制主问题是可行的(虽然任务覆盖约束可能无法满足,可能需要引入人工变量或使用其他启发式方法生成更好的初始列,这里为简化,我们假设初始模式集合能使限制主问题可行)。 求解限制主问题 :用线性规划求解器(如单纯形法)求解当前的限制主问题,得到当前最优解 λ* 和最重要的—— 对偶变量 的值。 设任务覆盖约束(Σ_ i Σ_ k d_ {jk}^i * λ_ {ik} = 1)对应的 对偶变量 为 π_ j (每个任务 j 一个)。 设凸组合约束(Σ_ k λ_ {ik} = 1)对应的 对偶变量 为 ω_ i (每个工人 i 一个)。 步骤5:定价子问题——寻找“有负检验数”的新列 在单纯形法中,引入一个新变量(列)是否有利,取决于它的 检验数 (Reduced Cost)是否为负(对于最小化问题)。在我们的MP模型中,对于工人 i 的一个新模式(其决策变量为 λ_ {i, new}),其检验数计算如下: 检验数 = c_ {i, new} - Σ_ j π_ j * d_ {j, new}^i - ω_ i 其中,c_ {i, new} = Σ_ j c_ {ij} * x_ {ij},d_ {j, new}^i = x_ {ij}。所以, 检验数 = Σ_ j c_ {ij} * x_ {ij} - Σ_ j π_ j * x_ {ij} - ω_ i = Σ_ j (c_ {ij} - π_ j) * x_ {ij} - ω_ i 我们的目标是:对于每个工人 i,尝试找到一个可行的新模式(即一组满足工人 i 自身能力约束的 x_ {ij} ≥ 0),使得其检验数为负。 如果找到了,将这个新模式作为一列加入限制主问题,因为它有可能改进当前解。 这引导出 m 个独立的 定价子问题 ,每个对应一个工人 i: 定价子问题 i(对工人 i) : Minimize Σ_ {j∈J} (c_ {ij} - π_ j) * x_ {ij} - ω_ i Subject to: Σ_ {j∈J} a_ {ij} * x_ {ij} ≤ b_ i x_ {ij} ≥ 0, ∀ j ∈ J 注意,ω_ i 是一个常数。所以, 子问题 i 等价于求解一个简单的线性规划(实际上是一个连续背包问题) : Minimize Σ_ {j∈J} (c_ {ij} - π_ j) * x_ {ij} Subject to: Σ_ {j∈J} a_ {ij} * x_ {ij} ≤ b_ i x_ {ij} ≥ 0, ∀ j ∈ J 步骤6:列生成迭代过程 求解当前限制主问题 ,得到对偶变量 π 和 ω。 遍历所有工人 i ,求解对应的定价子问题 i。 检查终止条件 :如果对于 所有 工人 i,其定价子问题的最优值(即最小化的 Σ_ j (c_ {ij} - π_ j) * x_ {ij})都 大于等于 ω_ i ,那么意味着所有可能新列的检验数都 ≥ 0。根据线性规划的对偶理论,当前限制主问题的解就是 原主问题(MP)的最优解 ,算法终止。 添加新列 :如果存在某个工人 i’,其定价子问题的最优值 < ω_ i’,那么我们就找到了一个检验数为负的新模式(由子问题的最优解 x_ {ij}^* 定义)。将这个新模式添加到限制主问题中,对应工人 i’ 的新变量 λ_ {i’, new}。其在该列中的系数为: 在目标函数中的系数:c_ {i’, new} = Σ_ j c_ {i’j} * x_ {i’j}^* 在任务覆盖约束 j 中的系数:d_ {j, new}^{i’} = x_ {i’j}^* 在凸组合约束 i’ 中的系数:1 回到第1步,用新的列集合求解更新后的限制主问题。 步骤7:获取原问题解 当列生成算法终止时,我们得到了限制主问题(现在等价于完整MP)的最优解 λ* 。我们需要将其“转换”回原问题的 x_ {ij} 变量。 原问题的解为:对于每个工人 i 和任务 j,x* {ij} = Σ {k∈S_ i} (x_ {ij}^k * λ_ {ik}^ ),其中 x_ {ij}^k 是模式 k 中工人 i 处理任务 j 的量,λ_ {ik}^ 是主问题中该模式的使用程度。 总结与核心思路 这个算法的美妙之处在于,它将一个大规模线性规划(变量数为 m* n)分解为: 一个“主协调”问题 :负责确保所有任务被覆盖,并协调各工人的模式选择。其规模只与任务数 n 和当前生成的列数有关,通常远小于 m* n。 m 个“局部”子问题 :每个子问题只关心一个工人,结构简单(一个资源约束的线性规划),可以 并行高效求解 。 通过迭代地在“主问题”和“子问题”之间交换对偶信息(π_ j),算法能够“按需”生成有价值的决策模式,避免了对所有 m* n 种可能的分配组合进行显式枚举和存储,从而能够高效求解大规模的广义指派问题。这正是列生成和Dantzig-Wolfe分解在处理具有可分离结构的大规模线性规划时的威力所在。