您可以用最低成本流解析器 作業問題。
事實上,最低成本流程傳回解決方案時,通常比 MIP 或 MIP CP-SAT 解題工具。不過,MIP 和 CP-SAT 可以解決的 因此在大多數情況下,MIP 或 CP-SAT 是最佳選擇,
下列各節將說明可解決下列問題的 Python 程式 使用最低費用流程解題工具指派問題:
線性指派範例
本節說明如何解決範例,詳情請參閱本節中的說明 線性指派求解器 (分鐘) 成本流程問題
匯入程式庫
下列程式碼會匯入所需的程式庫。
Python
from ortools.graph.python import min_cost_flow
C++
#include <cstdint> #include <vector> #include "ortools/graph/min_cost_flow.h"
Java
import com.google.ortools.Loader; import com.google.ortools.graph.MinCostFlow; import com.google.ortools.graph.MinCostFlowBase;
C#
using System; using Google.OrTools.Graph;
宣告解題工具
下列程式碼會建立最低成本流量解析器。
Python
# Instantiate a SimpleMinCostFlow solver. smcf = min_cost_flow.SimpleMinCostFlow()
C++
// Instantiate a SimpleMinCostFlow solver. SimpleMinCostFlow min_cost_flow;
Java
// Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow();
C#
// Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow();
建立資料
問題的流程圖包含費用的雙部分圖 矩陣 (請參閱 作業總覽 但來源和接收器已新增
資料包含下列四個陣列 (對應至起始節點) 終止節點、容量及問題成本每個陣列的長度為 代表圖表中的弧形數量
Python
# Define the directed graph for the flow. start_nodes = ( [0, 0, 0, 0] + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + [5, 6, 7, 8] ) end_nodes = ( [1, 2, 3, 4] + [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8] + [9, 9, 9, 9] ) capacities = ( [1, 1, 1, 1] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + [1, 1, 1, 1] ) costs = ( [0, 0, 0, 0] + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115] + [0, 0, 0, 0] ) source = 0 sink = 9 tasks = 4 supplies = [tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks]
C++
// Define four parallel arrays: sources, destinations, capacities, // and unit costs between each pair. For instance, the arc from node 0 // to node 1 has a capacity of 15. const std::vector<int64_t> start_nodes = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8}; const std::vector<int64_t> end_nodes = {1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 9, 9, 9, 9}; const std::vector<int64_t> capacities = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; const std::vector<int64_t> unit_costs = {0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 0, 0, 0, 0}; const int64_t source = 0; const int64_t sink = 9; const int64_t tasks = 4; // Define an array of supplies at each node. const std::vector<int64_t> supplies = {tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks};
Java
// Define four parallel arrays: sources, destinations, capacities, and unit costs // between each pair. int[] startNodes = new int[] {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8}; int[] endNodes = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 9, 9, 9, 9}; int[] capacities = new int[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; int[] unitCosts = new int[] { 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 0, 0, 0, 0}; int source = 0; int sink = 9; int tasks = 4; // Define an array of supplies at each node. int[] supplies = new int[] {tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks};
C#
// Define four parallel arrays: sources, destinations, capacities, and unit costs // between each pair. int[] startNodes = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8 }; int[] endNodes = { 1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 9, 9, 9, 9 }; int[] capacities = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int[] unitCosts = { 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 0, 0, 0, 0 }; int source = 0; int sink = 9; int tasks = 4; // Define an array of supplies at each node. int[] supplies = { tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks };
為了清楚設定資料的方式,每個陣列會分為三個 子陣列:
- 第一個陣列對應到從來源外的弧形對應。
- 第二個陣列則對應至工作站和工作之間的弧線。
對於
costs
來說,這只是 費用矩陣 (用於線性指派解題工具) 合併成向量 - 第三個陣列則對應至導向接收器的弧形。
資料也包含向量 supplies
,它會提供每個向量
節點。
最低費用流程問題如何表示指派項目問題
上述的最低費用流程問題如何呈現指派問題?首先 由於每個弧形的容量為 1,因此每個弧線的供應率為 4 會引導工作站進行 1 段流程。
接下來,Flow-in-equals-flow-out 條件會強制將資料流出每個工作站 設為 1。如果可以的話,解題工具會盡可能以最低成本處理該資料流動 以弧線表示每個 worker。然而,解題工具無法 處理單一工作如果確實如此, 但該工作無法在單一架構上傳送 容量 1 這表示解題工具只能將工作指派給單一工作站,如 會產生作業問題
最後,「Flow-in-equals-flow-out」條件會強制每個任務 ,因此每項工作都是由某些 worker 執行。
建立圖表和限制
下列程式碼會建立圖表和限制。
Python
# Add each arc. for i in range(len(start_nodes)): smcf.add_arc_with_capacity_and_unit_cost( start_nodes[i], end_nodes[i], capacities[i], costs[i] ) # Add node supplies. for i in range(len(supplies)): smcf.set_node_supply(i, supplies[i])
C++
// Add each arc. for (int i = 0; i < start_nodes.size(); ++i) { int arc = min_cost_flow.AddArcWithCapacityAndUnitCost( start_nodes[i], end_nodes[i], capacities[i], unit_costs[i]); if (arc != i) LOG(FATAL) << "Internal error"; } // Add node supplies. for (int i = 0; i < supplies.size(); ++i) { min_cost_flow.SetNodeSupply(i, supplies[i]); }
Java
// Add each arc. for (int i = 0; i < startNodes.length; ++i) { int arc = minCostFlow.addArcWithCapacityAndUnitCost( startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) { throw new Exception("Internal error"); } } // Add node supplies. for (int i = 0; i < supplies.length; ++i) { minCostFlow.setNodeSupply(i, supplies[i]); }
C#
// Add each arc. for (int i = 0; i < startNodes.Length; ++i) { int arc = minCostFlow.AddArcWithCapacityAndUnitCost(startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) throw new Exception("Internal error"); } // Add node supplies. for (int i = 0; i < supplies.Length; ++i) { minCostFlow.SetNodeSupply(i, supplies[i]); }
叫用解題工具
下列程式碼會叫用解題工具並顯示解決方案。
Python
# Find the minimum cost flow between node 0 and node 10. status = smcf.solve()
C++
// Find the min cost flow. int status = min_cost_flow.Solve();
Java
// Find the min cost flow. MinCostFlowBase.Status status = minCostFlow.solve();
C#
// Find the min cost flow. MinCostFlow.Status status = minCostFlow.Solve();
解決方案包含工作站和獲派工作之間的弧線 以 1 流處理。(連接至來源或接收器的弧形不屬於這些項目) 解決方案)。
程式會檢查每個弧線是否都有流程 1,如果有,就會輸出
弧形的 Tail
(起始節點) 和 Head
(結束節點),對應於
定義工作中的處理程序與工作
計畫輸出內容
Python
if status == smcf.OPTIMAL: print("Total cost = ", smcf.optimal_cost()) print() for arc in range(smcf.num_arcs()): # Can ignore arcs leading out of source or into sink. if smcf.tail(arc) != source and smcf.head(arc) != sink: # Arcs in the solution have a flow value of 1. Their start and end nodes # give an assignment of worker to task. if smcf.flow(arc) > 0: print( "Worker %d assigned to task %d. Cost = %d" % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)) ) else: print("There was an issue with the min cost flow input.") print(f"Status: {status}")
C++
if (status == MinCostFlow::OPTIMAL) { LOG(INFO) << "Total cost: " << min_cost_flow.OptimalCost(); LOG(INFO) << ""; for (std::size_t i = 0; i < min_cost_flow.NumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (min_cost_flow.Tail(i) != source && min_cost_flow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end // nodes give an assignment of worker to task. if (min_cost_flow.Flow(i) > 0) { LOG(INFO) << "Worker " << min_cost_flow.Tail(i) << " assigned to task " << min_cost_flow.Head(i) << " Cost: " << min_cost_flow.UnitCost(i); } } } } else { LOG(INFO) << "Solving the min cost flow problem failed."; LOG(INFO) << "Solver status: " << status; }
Java
if (status == MinCostFlow.Status.OPTIMAL) { System.out.println("Total cost: " + minCostFlow.getOptimalCost()); System.out.println(); for (int i = 0; i < minCostFlow.getNumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (minCostFlow.getTail(i) != source && minCostFlow.getHead(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.getFlow(i) > 0) { System.out.println("Worker " + minCostFlow.getTail(i) + " assigned to task " + minCostFlow.getHead(i) + " Cost: " + minCostFlow.getUnitCost(i)); } } } } else { System.out.println("Solving the min cost flow problem failed."); System.out.println("Solver status: " + status); }
C#
if (status == MinCostFlow.Status.OPTIMAL) { Console.WriteLine("Total cost: " + minCostFlow.OptimalCost()); Console.WriteLine(""); for (int i = 0; i < minCostFlow.NumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (minCostFlow.Tail(i) != source && minCostFlow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.Flow(i) > 0) { Console.WriteLine("Worker " + minCostFlow.Tail(i) + " assigned to task " + minCostFlow.Head(i) + " Cost: " + minCostFlow.UnitCost(i)); } } } } else { Console.WriteLine("Solving the min cost flow problem failed."); Console.WriteLine("Solver status: " + status); }
以下是程式的輸出內容。
Total cost = 265 Worker 1 assigned to task 8. Cost = 70 Worker 2 assigned to task 7. Cost = 55 Worker 3 assigned to task 6. Cost = 95 Worker 4 assigned to task 5. Cost = 45 Time = 0.000245 seconds
結果會與 線性指派解題工具 ( 工作站數量和費用)。線性指派解題工具速度較快 比最低成本流 — 0.000147 秒與 0.000458 秒
整個計畫
下方顯示整套計畫。
Python
"""Linear assignment example.""" from ortools.graph.python import min_cost_flow def main(): """Solving an Assignment Problem with MinCostFlow.""" # Instantiate a SimpleMinCostFlow solver. smcf = min_cost_flow.SimpleMinCostFlow() # Define the directed graph for the flow. start_nodes = ( [0, 0, 0, 0] + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4] + [5, 6, 7, 8] ) end_nodes = ( [1, 2, 3, 4] + [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8] + [9, 9, 9, 9] ) capacities = ( [1, 1, 1, 1] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + [1, 1, 1, 1] ) costs = ( [0, 0, 0, 0] + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115] + [0, 0, 0, 0] ) source = 0 sink = 9 tasks = 4 supplies = [tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks] # Add each arc. for i in range(len(start_nodes)): smcf.add_arc_with_capacity_and_unit_cost( start_nodes[i], end_nodes[i], capacities[i], costs[i] ) # Add node supplies. for i in range(len(supplies)): smcf.set_node_supply(i, supplies[i]) # Find the minimum cost flow between node 0 and node 10. status = smcf.solve() if status == smcf.OPTIMAL: print("Total cost = ", smcf.optimal_cost()) print() for arc in range(smcf.num_arcs()): # Can ignore arcs leading out of source or into sink. if smcf.tail(arc) != source and smcf.head(arc) != sink: # Arcs in the solution have a flow value of 1. Their start and end nodes # give an assignment of worker to task. if smcf.flow(arc) > 0: print( "Worker %d assigned to task %d. Cost = %d" % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)) ) else: print("There was an issue with the min cost flow input.") print(f"Status: {status}") if __name__ == "__main__": main()
C++
#include <cstdint> #include <vector> #include "ortools/graph/min_cost_flow.h" namespace operations_research { // MinCostFlow simple interface example. void AssignmentMinFlow() { // Instantiate a SimpleMinCostFlow solver. SimpleMinCostFlow min_cost_flow; // Define four parallel arrays: sources, destinations, capacities, // and unit costs between each pair. For instance, the arc from node 0 // to node 1 has a capacity of 15. const std::vector<int64_t> start_nodes = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8}; const std::vector<int64_t> end_nodes = {1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 9, 9, 9, 9}; const std::vector<int64_t> capacities = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; const std::vector<int64_t> unit_costs = {0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 0, 0, 0, 0}; const int64_t source = 0; const int64_t sink = 9; const int64_t tasks = 4; // Define an array of supplies at each node. const std::vector<int64_t> supplies = {tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks}; // Add each arc. for (int i = 0; i < start_nodes.size(); ++i) { int arc = min_cost_flow.AddArcWithCapacityAndUnitCost( start_nodes[i], end_nodes[i], capacities[i], unit_costs[i]); if (arc != i) LOG(FATAL) << "Internal error"; } // Add node supplies. for (int i = 0; i < supplies.size(); ++i) { min_cost_flow.SetNodeSupply(i, supplies[i]); } // Find the min cost flow. int status = min_cost_flow.Solve(); if (status == MinCostFlow::OPTIMAL) { LOG(INFO) << "Total cost: " << min_cost_flow.OptimalCost(); LOG(INFO) << ""; for (std::size_t i = 0; i < min_cost_flow.NumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (min_cost_flow.Tail(i) != source && min_cost_flow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end // nodes give an assignment of worker to task. if (min_cost_flow.Flow(i) > 0) { LOG(INFO) << "Worker " << min_cost_flow.Tail(i) << " assigned to task " << min_cost_flow.Head(i) << " Cost: " << min_cost_flow.UnitCost(i); } } } } else { LOG(INFO) << "Solving the min cost flow problem failed."; LOG(INFO) << "Solver status: " << status; } } } // namespace operations_research int main() { operations_research::AssignmentMinFlow(); return EXIT_SUCCESS; }
Java
package com.google.ortools.graph.samples; import com.google.ortools.Loader; import com.google.ortools.graph.MinCostFlow; import com.google.ortools.graph.MinCostFlowBase; /** Minimal Assignment Min Flow. */ public class AssignmentMinFlow { public static void main(String[] args) throws Exception { Loader.loadNativeLibraries(); // Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow(); // Define four parallel arrays: sources, destinations, capacities, and unit costs // between each pair. int[] startNodes = new int[] {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8}; int[] endNodes = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 9, 9, 9, 9}; int[] capacities = new int[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; int[] unitCosts = new int[] { 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 0, 0, 0, 0}; int source = 0; int sink = 9; int tasks = 4; // Define an array of supplies at each node. int[] supplies = new int[] {tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks}; // Add each arc. for (int i = 0; i < startNodes.length; ++i) { int arc = minCostFlow.addArcWithCapacityAndUnitCost( startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) { throw new Exception("Internal error"); } } // Add node supplies. for (int i = 0; i < supplies.length; ++i) { minCostFlow.setNodeSupply(i, supplies[i]); } // Find the min cost flow. MinCostFlowBase.Status status = minCostFlow.solve(); if (status == MinCostFlow.Status.OPTIMAL) { System.out.println("Total cost: " + minCostFlow.getOptimalCost()); System.out.println(); for (int i = 0; i < minCostFlow.getNumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (minCostFlow.getTail(i) != source && minCostFlow.getHead(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.getFlow(i) > 0) { System.out.println("Worker " + minCostFlow.getTail(i) + " assigned to task " + minCostFlow.getHead(i) + " Cost: " + minCostFlow.getUnitCost(i)); } } } } else { System.out.println("Solving the min cost flow problem failed."); System.out.println("Solver status: " + status); } } private AssignmentMinFlow() {} }
C#
using System; using Google.OrTools.Graph; public class AssignmentMinFlow { static void Main() { // Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow(); // Define four parallel arrays: sources, destinations, capacities, and unit costs // between each pair. int[] startNodes = { 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 6, 7, 8 }; int[] endNodes = { 1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 9, 9, 9, 9 }; int[] capacities = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int[] unitCosts = { 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 0, 0, 0, 0 }; int source = 0; int sink = 9; int tasks = 4; // Define an array of supplies at each node. int[] supplies = { tasks, 0, 0, 0, 0, 0, 0, 0, 0, -tasks }; // Add each arc. for (int i = 0; i < startNodes.Length; ++i) { int arc = minCostFlow.AddArcWithCapacityAndUnitCost(startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) throw new Exception("Internal error"); } // Add node supplies. for (int i = 0; i < supplies.Length; ++i) { minCostFlow.SetNodeSupply(i, supplies[i]); } // Find the min cost flow. MinCostFlow.Status status = minCostFlow.Solve(); if (status == MinCostFlow.Status.OPTIMAL) { Console.WriteLine("Total cost: " + minCostFlow.OptimalCost()); Console.WriteLine(""); for (int i = 0; i < minCostFlow.NumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (minCostFlow.Tail(i) != source && minCostFlow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.Flow(i) > 0) { Console.WriteLine("Worker " + minCostFlow.Tail(i) + " assigned to task " + minCostFlow.Head(i) + " Cost: " + minCostFlow.UnitCost(i)); } } } } else { Console.WriteLine("Solving the min cost flow problem failed."); Console.WriteLine("Solver status: " + status); } } }
與員工團隊進行指派作業
本節提供的作業問題較為常見。本問題中 工作人員分為兩個團隊問題是指派四個工作給 讓團隊之間的工作負載平等, 因此每個團隊會執行兩項工作
如需 MIP 解題工具解決方案來解決這個問題,請參閱 與工作人員團隊進行指派。
以下各節將介紹如何使用分下限值解決問題的程式 來解決
匯入程式庫
下列程式碼會匯入所需的程式庫。
Python
from ortools.graph.python import min_cost_flow
C++
#include <cstdint> #include <vector> #include "ortools/graph/min_cost_flow.h"
Java
import com.google.ortools.Loader; import com.google.ortools.graph.MinCostFlow; import com.google.ortools.graph.MinCostFlowBase;
C#
using System; using Google.OrTools.Graph;
宣告解題工具
下列程式碼會建立最低成本流量解析器。
Python
smcf = min_cost_flow.SimpleMinCostFlow()
C++
// Instantiate a SimpleMinCostFlow solver. SimpleMinCostFlow min_cost_flow;
Java
// Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow();
C#
// Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow();
建立資料
下列程式碼會建立該程式的資料。
Python
# Define the directed graph for the flow. team_a = [1, 3, 5] team_b = [2, 4, 6] start_nodes = ( # fmt: off [0, 0] + [11, 11, 11] + [12, 12, 12] + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6] + [7, 8, 9, 10] # fmt: on ) end_nodes = ( # fmt: off [11, 12] + team_a + team_b + [7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10] + [13, 13, 13, 13] # fmt: on ) capacities = ( # fmt: off [2, 2] + [1, 1, 1] + [1, 1, 1] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + [1, 1, 1, 1] # fmt: on ) costs = ( # fmt: off [0, 0] + [0, 0, 0] + [0, 0, 0] + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95] + [0, 0, 0, 0] # fmt: on ) source = 0 sink = 13 tasks = 4 # Define an array of supplies at each node. supplies = [tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks]
C++
// Define the directed graph for the flow. const std::vector<int64_t> team_A = {1, 3, 5}; const std::vector<int64_t> team_B = {2, 4, 6}; const std::vector<int64_t> start_nodes = { 0, 0, 11, 11, 11, 12, 12, 12, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 8, 9, 10}; const std::vector<int64_t> end_nodes = { 11, 12, 1, 3, 5, 2, 4, 6, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 13, 13, 13, 13}; const std::vector<int64_t> capacities = {2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; const std::vector<int64_t> unit_costs = { 0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95, 0, 0, 0, 0}; const int64_t source = 0; const int64_t sink = 13; const int64_t tasks = 4; // Define an array of supplies at each node. const std::vector<int64_t> supplies = {tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks};
Java
// Define the directed graph for the flow. // int[] teamA = new int[] {1, 3, 5}; // int[] teamB = new int[] {2, 4, 6}; int[] startNodes = new int[] {0, 0, 11, 11, 11, 12, 12, 12, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 8, 9, 10}; int[] endNodes = new int[] {11, 12, 1, 3, 5, 2, 4, 6, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 13, 13, 13, 13}; int[] capacities = new int[] {2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; int[] unitCosts = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95, 0, 0, 0, 0}; int source = 0; int sink = 13; int tasks = 4; // Define an array of supplies at each node. int[] supplies = new int[] {tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks};
C#
// Define the directed graph for the flow. int[] teamA = { 1, 3, 5 }; int[] teamB = { 2, 4, 6 }; // Define four parallel arrays: sources, destinations, capacities, and unit costs // between each pair. int[] startNodes = { 0, 0, 11, 11, 11, 12, 12, 12, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 8, 9, 10 }; int[] endNodes = { 11, 12, 1, 3, 5, 2, 4, 6, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 13, 13, 13, 13 }; int[] capacities = { 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int[] unitCosts = { 0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95, 0, 0, 0, 0 }; int source = 0; int sink = 13; int tasks = 4; // Define an array of supplies at each node. int[] supplies = { tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks };
工作站會對應節點 1 到 6。團隊 A 包含員工 1、3 和 5 團隊 B 則由工作人員 2、4 和 6 組成工作編號為 7 到 10。
來源和工作站之間會有兩個新節點,分別為 11 和 12。節點 11 為 分別連至團隊 A 的節點,節點 12 則連線至 等 G 團隊,容量為 1 下圖只顯示從來源到工作站的節點和弧形。
平衡工作負載的關鍵在於來源 0 會連線至節點 11 以及 12 乘以容量 2這代表節點 11 和 12 (進而 團隊 A 和 B) 最多只能有 2 層。 因此,每個團隊最多只能執行兩項工作。
建立限制
Python
# Add each arc. for i in range(0, len(start_nodes)): smcf.add_arc_with_capacity_and_unit_cost( start_nodes[i], end_nodes[i], capacities[i], costs[i] ) # Add node supplies. for i in range(0, len(supplies)): smcf.set_node_supply(i, supplies[i])
C++
// Add each arc. for (int i = 0; i < start_nodes.size(); ++i) { int arc = min_cost_flow.AddArcWithCapacityAndUnitCost( start_nodes[i], end_nodes[i], capacities[i], unit_costs[i]); if (arc != i) LOG(FATAL) << "Internal error"; } // Add node supplies. for (int i = 0; i < supplies.size(); ++i) { min_cost_flow.SetNodeSupply(i, supplies[i]); }
Java
// Add each arc. for (int i = 0; i < startNodes.length; ++i) { int arc = minCostFlow.addArcWithCapacityAndUnitCost( startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) { throw new Exception("Internal error"); } } // Add node supplies. for (int i = 0; i < supplies.length; ++i) { minCostFlow.setNodeSupply(i, supplies[i]); }
C#
// Add each arc. for (int i = 0; i < startNodes.Length; ++i) { int arc = minCostFlow.AddArcWithCapacityAndUnitCost(startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) throw new Exception("Internal error"); } // Add node supplies. for (int i = 0; i < supplies.Length; ++i) { minCostFlow.SetNodeSupply(i, supplies[i]); }
叫用解題工具
Python
# Find the minimum cost flow between node 0 and node 10. status = smcf.solve()
C++
// Find the min cost flow. int status = min_cost_flow.Solve();
Java
// Find the min cost flow. MinCostFlowBase.Status status = minCostFlow.solve();
C#
// Find the min cost flow. MinCostFlow.Status status = minCostFlow.Solve();
計畫輸出內容
Python
if status == smcf.OPTIMAL: print("Total cost = ", smcf.optimal_cost()) print() for arc in range(smcf.num_arcs()): # Can ignore arcs leading out of source or intermediate, or into sink. if ( smcf.tail(arc) != source and smcf.tail(arc) != 11 and smcf.tail(arc) != 12 and smcf.head(arc) != sink ): # Arcs in the solution will have a flow value of 1. # There start and end nodes give an assignment of worker to task. if smcf.flow(arc) > 0: print( "Worker %d assigned to task %d. Cost = %d" % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)) ) else: print("There was an issue with the min cost flow input.") print(f"Status: {status}")
C++
if (status == MinCostFlow::OPTIMAL) { LOG(INFO) << "Total cost: " << min_cost_flow.OptimalCost(); LOG(INFO) << ""; for (std::size_t i = 0; i < min_cost_flow.NumArcs(); ++i) { // Can ignore arcs leading out of source or intermediate nodes, or into // sink. if (min_cost_flow.Tail(i) != source && min_cost_flow.Tail(i) != 11 && min_cost_flow.Tail(i) != 12 && min_cost_flow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end // nodes give an assignment of worker to task. if (min_cost_flow.Flow(i) > 0) { LOG(INFO) << "Worker " << min_cost_flow.Tail(i) << " assigned to task " << min_cost_flow.Head(i) << " Cost: " << min_cost_flow.UnitCost(i); } } } } else { LOG(INFO) << "Solving the min cost flow problem failed."; LOG(INFO) << "Solver status: " << status; }
Java
if (status == MinCostFlow.Status.OPTIMAL) { System.out.println("Total cost: " + minCostFlow.getOptimalCost()); System.out.println(); for (int i = 0; i < minCostFlow.getNumArcs(); ++i) { // Can ignore arcs leading out of source or intermediate nodes, or into sink. if (minCostFlow.getTail(i) != source && minCostFlow.getTail(i) != 11 && minCostFlow.getTail(i) != 12 && minCostFlow.getHead(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.getFlow(i) > 0) { System.out.println("Worker " + minCostFlow.getTail(i) + " assigned to task " + minCostFlow.getHead(i) + " Cost: " + minCostFlow.getUnitCost(i)); } } } } else { System.out.println("Solving the min cost flow problem failed."); System.out.println("Solver status: " + status); }
C#
if (status == MinCostFlow.Status.OPTIMAL) { Console.WriteLine("Total cost: " + minCostFlow.OptimalCost()); Console.WriteLine(""); for (int i = 0; i < minCostFlow.NumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (minCostFlow.Tail(i) != source && minCostFlow.Tail(i) != 11 && minCostFlow.Tail(i) != 12 && minCostFlow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.Flow(i) > 0) { Console.WriteLine("Worker " + minCostFlow.Tail(i) + " assigned to task " + minCostFlow.Head(i) + " Cost: " + minCostFlow.UnitCost(i)); } } } } else { Console.WriteLine("Solving the min cost flow problem failed."); Console.WriteLine("Solver status: " + status); }
以下顯示程式的輸出內容。
Total cost = 250 Worker 1 assigned to task 9. Cost = 75 Worker 2 assigned to task 7. Cost = 35 Worker 5 assigned to task 10. Cost = 75 Worker 6 assigned to task 8. Cost = 65 Time = 0.00031 seconds
團隊 A 指派給工作 9 和 10,團隊 B 則獲派工作 7 和 8。
請注意,解決這個問題時 MIP 解析器, 大約需要 0.006 秒
整個計畫
下方顯示整套計畫。
Python
"""Assignment with teams of workers.""" from ortools.graph.python import min_cost_flow def main(): """Solving an Assignment with teams of worker.""" smcf = min_cost_flow.SimpleMinCostFlow() # Define the directed graph for the flow. team_a = [1, 3, 5] team_b = [2, 4, 6] start_nodes = ( # fmt: off [0, 0] + [11, 11, 11] + [12, 12, 12] + [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6] + [7, 8, 9, 10] # fmt: on ) end_nodes = ( # fmt: off [11, 12] + team_a + team_b + [7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10] + [13, 13, 13, 13] # fmt: on ) capacities = ( # fmt: off [2, 2] + [1, 1, 1] + [1, 1, 1] + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + [1, 1, 1, 1] # fmt: on ) costs = ( # fmt: off [0, 0] + [0, 0, 0] + [0, 0, 0] + [90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95] + [0, 0, 0, 0] # fmt: on ) source = 0 sink = 13 tasks = 4 # Define an array of supplies at each node. supplies = [tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks] # Add each arc. for i in range(0, len(start_nodes)): smcf.add_arc_with_capacity_and_unit_cost( start_nodes[i], end_nodes[i], capacities[i], costs[i] ) # Add node supplies. for i in range(0, len(supplies)): smcf.set_node_supply(i, supplies[i]) # Find the minimum cost flow between node 0 and node 10. status = smcf.solve() if status == smcf.OPTIMAL: print("Total cost = ", smcf.optimal_cost()) print() for arc in range(smcf.num_arcs()): # Can ignore arcs leading out of source or intermediate, or into sink. if ( smcf.tail(arc) != source and smcf.tail(arc) != 11 and smcf.tail(arc) != 12 and smcf.head(arc) != sink ): # Arcs in the solution will have a flow value of 1. # There start and end nodes give an assignment of worker to task. if smcf.flow(arc) > 0: print( "Worker %d assigned to task %d. Cost = %d" % (smcf.tail(arc), smcf.head(arc), smcf.unit_cost(arc)) ) else: print("There was an issue with the min cost flow input.") print(f"Status: {status}") if __name__ == "__main__": main()
C++
#include <cstdint> #include <vector> #include "ortools/graph/min_cost_flow.h" namespace operations_research { // MinCostFlow simple interface example. void BalanceMinFlow() { // Instantiate a SimpleMinCostFlow solver. SimpleMinCostFlow min_cost_flow; // Define the directed graph for the flow. const std::vector<int64_t> team_A = {1, 3, 5}; const std::vector<int64_t> team_B = {2, 4, 6}; const std::vector<int64_t> start_nodes = { 0, 0, 11, 11, 11, 12, 12, 12, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 8, 9, 10}; const std::vector<int64_t> end_nodes = { 11, 12, 1, 3, 5, 2, 4, 6, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 13, 13, 13, 13}; const std::vector<int64_t> capacities = {2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; const std::vector<int64_t> unit_costs = { 0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95, 0, 0, 0, 0}; const int64_t source = 0; const int64_t sink = 13; const int64_t tasks = 4; // Define an array of supplies at each node. const std::vector<int64_t> supplies = {tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks}; // Add each arc. for (int i = 0; i < start_nodes.size(); ++i) { int arc = min_cost_flow.AddArcWithCapacityAndUnitCost( start_nodes[i], end_nodes[i], capacities[i], unit_costs[i]); if (arc != i) LOG(FATAL) << "Internal error"; } // Add node supplies. for (int i = 0; i < supplies.size(); ++i) { min_cost_flow.SetNodeSupply(i, supplies[i]); } // Find the min cost flow. int status = min_cost_flow.Solve(); if (status == MinCostFlow::OPTIMAL) { LOG(INFO) << "Total cost: " << min_cost_flow.OptimalCost(); LOG(INFO) << ""; for (std::size_t i = 0; i < min_cost_flow.NumArcs(); ++i) { // Can ignore arcs leading out of source or intermediate nodes, or into // sink. if (min_cost_flow.Tail(i) != source && min_cost_flow.Tail(i) != 11 && min_cost_flow.Tail(i) != 12 && min_cost_flow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end // nodes give an assignment of worker to task. if (min_cost_flow.Flow(i) > 0) { LOG(INFO) << "Worker " << min_cost_flow.Tail(i) << " assigned to task " << min_cost_flow.Head(i) << " Cost: " << min_cost_flow.UnitCost(i); } } } } else { LOG(INFO) << "Solving the min cost flow problem failed."; LOG(INFO) << "Solver status: " << status; } } } // namespace operations_research int main() { operations_research::BalanceMinFlow(); return EXIT_SUCCESS; }
Java
package com.google.ortools.graph.samples; import com.google.ortools.Loader; import com.google.ortools.graph.MinCostFlow; import com.google.ortools.graph.MinCostFlowBase; /** Minimal Assignment Min Flow. */ public class BalanceMinFlow { public static void main(String[] args) throws Exception { Loader.loadNativeLibraries(); // Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow(); // Define the directed graph for the flow. // int[] teamA = new int[] {1, 3, 5}; // int[] teamB = new int[] {2, 4, 6}; int[] startNodes = new int[] {0, 0, 11, 11, 11, 12, 12, 12, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 8, 9, 10}; int[] endNodes = new int[] {11, 12, 1, 3, 5, 2, 4, 6, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 13, 13, 13, 13}; int[] capacities = new int[] {2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; int[] unitCosts = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95, 0, 0, 0, 0}; int source = 0; int sink = 13; int tasks = 4; // Define an array of supplies at each node. int[] supplies = new int[] {tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks}; // Add each arc. for (int i = 0; i < startNodes.length; ++i) { int arc = minCostFlow.addArcWithCapacityAndUnitCost( startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) { throw new Exception("Internal error"); } } // Add node supplies. for (int i = 0; i < supplies.length; ++i) { minCostFlow.setNodeSupply(i, supplies[i]); } // Find the min cost flow. MinCostFlowBase.Status status = minCostFlow.solve(); if (status == MinCostFlow.Status.OPTIMAL) { System.out.println("Total cost: " + minCostFlow.getOptimalCost()); System.out.println(); for (int i = 0; i < minCostFlow.getNumArcs(); ++i) { // Can ignore arcs leading out of source or intermediate nodes, or into sink. if (minCostFlow.getTail(i) != source && minCostFlow.getTail(i) != 11 && minCostFlow.getTail(i) != 12 && minCostFlow.getHead(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.getFlow(i) > 0) { System.out.println("Worker " + minCostFlow.getTail(i) + " assigned to task " + minCostFlow.getHead(i) + " Cost: " + minCostFlow.getUnitCost(i)); } } } } else { System.out.println("Solving the min cost flow problem failed."); System.out.println("Solver status: " + status); } } private BalanceMinFlow() {} }
C#
using System; using Google.OrTools.Graph; public class BalanceMinFlow { static void Main() { // Instantiate a SimpleMinCostFlow solver. MinCostFlow minCostFlow = new MinCostFlow(); // Define the directed graph for the flow. int[] teamA = { 1, 3, 5 }; int[] teamB = { 2, 4, 6 }; // Define four parallel arrays: sources, destinations, capacities, and unit costs // between each pair. int[] startNodes = { 0, 0, 11, 11, 11, 12, 12, 12, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 8, 9, 10 }; int[] endNodes = { 11, 12, 1, 3, 5, 2, 4, 6, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 7, 8, 9, 10, 13, 13, 13, 13 }; int[] capacities = { 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; int[] unitCosts = { 0, 0, 0, 0, 0, 0, 0, 0, 90, 76, 75, 70, 35, 85, 55, 65, 125, 95, 90, 105, 45, 110, 95, 115, 60, 105, 80, 75, 45, 65, 110, 95, 0, 0, 0, 0 }; int source = 0; int sink = 13; int tasks = 4; // Define an array of supplies at each node. int[] supplies = { tasks, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -tasks }; // Add each arc. for (int i = 0; i < startNodes.Length; ++i) { int arc = minCostFlow.AddArcWithCapacityAndUnitCost(startNodes[i], endNodes[i], capacities[i], unitCosts[i]); if (arc != i) throw new Exception("Internal error"); } // Add node supplies. for (int i = 0; i < supplies.Length; ++i) { minCostFlow.SetNodeSupply(i, supplies[i]); } // Find the min cost flow. MinCostFlow.Status status = minCostFlow.Solve(); if (status == MinCostFlow.Status.OPTIMAL) { Console.WriteLine("Total cost: " + minCostFlow.OptimalCost()); Console.WriteLine(""); for (int i = 0; i < minCostFlow.NumArcs(); ++i) { // Can ignore arcs leading out of source or into sink. if (minCostFlow.Tail(i) != source && minCostFlow.Tail(i) != 11 && minCostFlow.Tail(i) != 12 && minCostFlow.Head(i) != sink) { // Arcs in the solution have a flow value of 1. Their start and end nodes // give an assignment of worker to task. if (minCostFlow.Flow(i) > 0) { Console.WriteLine("Worker " + minCostFlow.Tail(i) + " assigned to task " + minCostFlow.Head(i) + " Cost: " + minCostFlow.UnitCost(i)); } } } } else { Console.WriteLine("Solving the min cost flow problem failed."); Console.WriteLine("Solver status: " + status); } } }