最小費用流問題
最小費用流問題を最短路を使って解く方法。
LPで解けるが、LPソルバを使いたくないときに使える。
- 最短路を求めて、流せるだけ流す。
- 流したら、減らせるので逆向きのアークにもなるが、コストは負になる。
- コストが負だとDijikstraが使えないので、開始ノードからのコストで補正する (補正コスト=コスト+元ノードのP値ー先ノードのP値)。P値は開始ノードからのコストの累積値。
- ロジックがシンプルになるので、開始用のノード(st)と終了用のノード(en)を追加した。
- 計算速度よりわかりやすさを優先。
Graphクラスは、ORToolBoxより。
using McfGraph = Graph<McfNode, McfArc>; using McfNTag = NodeTag<McfNode, McfArc>; using McfATag = ArcTag<McfNode, McfArc>; /// <summary> /// MinCostFlowProblem用 /// </summary> public sealed class McfNode { public int Request; // 要求量(正なら供給、負なら需要) public int Result; // 結果量(正なら供給、負なら需要) /// <summary>コンストラクタ</summary> public McfNode(int r) { Request = r; } } /// <summary> /// MinCostFlowProblem用 /// </summary> public sealed class McfArc { public int Capa; // 容量 public int Flow; // 流量(結果) public double Cost; // コスト /// <summary>コンストラクタ</summary> public McfArc(int cap, double cst) { Capa = cap; Flow = 0; Cost = cst; } public int Residue(bool b) { return b ? Capa - Flow : Flow; } } public static class MinCostFlowProblem { /// <summary> /// 最小費用流問題を解く /// </summary> /// <param name="g">グラフ</param> /// <param name="cost">コストの総和</param> /// <returns>全て流せたか</returns> public static bool Calc(McfGraph g, out double cost) { foreach (McfATag at in g.ArcTags) if (!at.IsDirected) throw new ApplicationException("Must be directed"); // ダミーノード追加 McfNTag st = g.AddNode(new McfNode(0)); // 供給ノード McfNTag en = g.AddNode(new McfNode(0)); // 需要ノード int n = g.NodesCount; // ノード数 // ダミーアーク追加 foreach (McfNTag nt in g.NodeTags) { if (nt == st) break; nt.Node.Result = 0; if (nt.Node.Request > 0) { st.Node.Request += nt.Node.Request; g.AddArc(new McfArc(nt.Node.Request, 0), st.Index, nt.Index, true); } else if (nt.Node.Request < 0) { en.Node.Request += nt.Node.Request; g.AddArc(new McfArc(-nt.Node.Request, 0), nt.Index, en.Index, true); } } double[] p = new double[n]; // 供給点からの最短値の累積 while (st.Node.Request > 0) { List<KeyValuePair<McfATag, bool>> minrt = MinRoute(g, p, st.Index, en.Index); if (minrt == null) break; // 流量計算 int flow = int.MaxValue; foreach (KeyValuePair<McfATag, bool> pr in minrt) flow = Math.Min(flow, pr.Key.Arc.Residue(pr.Value)); foreach (KeyValuePair<McfATag, bool> pr in minrt) pr.Key.Arc.Flow += pr.Value ? flow : -flow; st.Node.Request -= flow; en.Node.Request += flow; } // コスト計算 bool bCan = st.Node.Request == 0 && en.Node.Request == 0; foreach (McfATag at in st.OutArcTags) at.RightTag.Node.Result = at.Arc.Flow; foreach (McfATag at in en.InArcTags) at.LeftTag.Node.Result = -at.Arc.Flow; g.DelNode(en.Index); g.DelNode(st.Index); cost = 0; foreach (McfATag at in g.ArcTags) cost += at.Arc.Cost * at.Arc.Flow; return bCan; } private static List<KeyValuePair<McfATag, bool>> MinRoute (McfGraph g, double[] p, int n1, int n2) { if (n1 == n2) return null; McfATag[] prev = new McfATag[g.NodesCount]; double[] dist = new double[g.NodesCount]; for (int k = 0; k < dist.Length; ++k) dist[k] = double.PositiveInfinity; dist[n1] = 0; bool[] found = new bool[g.NodesCount]; int nxt = n1, nfound = 1; while (nfound < found.Length) { found[nxt] = true; ++nfound; foreach (McfATag at in g.NodeTag(nxt).OutArcTags) { int k = at.AnotherID(nxt); if (found[k] || at.Arc.Residue(true) == 0) continue; McfNTag nt = g.NodeTag(k); double d = at.Arc.Cost + p[at.LeftID] - p[at.RightID]; d += dist[nxt]; if (d < dist[k] || (d == dist[k] && prev[k] != null)) { prev[k] = at; dist[k] = d; } } foreach (McfATag at in g.NodeTag(nxt).InArcTags) { int k = at.AnotherID(nxt); if (found[k] || at.Arc.Residue(false) == 0) continue; McfNTag nt = g.NodeTag(k); double d = -at.Arc.Cost - p[at.LeftID] + p[at.RightID]; d += dist[nxt]; if (d < dist[k] || (d == dist[k] && prev[k] != null)) { prev[k] = at; dist[k] = d; } } double min = double.PositiveInfinity; for (int i = 0; i < dist.Length; ++i) { if (!found[i] && dist[i] < min) { min = dist[i]; nxt = i; } } if (min == double.PositiveInfinity) break; } if (dist[n2] == double.PositiveInfinity) return null; for (int i = 0; i < p.Length; ++i) p[i] += dist[i]; List<KeyValuePair<McfATag, bool>> res = new List<KeyValuePair<McfATag, bool>>(); int m = n2; while (m != n1) { res.Add(new KeyValuePair<McfATag, bool> (prev[m], prev[m].RightID == m)); m = prev[m].AnotherID(m); } res.Reverse(); return res; } public static void Test() { string[] ss = { "1,-1,-1,1/1,5,0,1;1,3,0,2;1,2,3,1;1,1,3,2/5", "1,0,0,-1,0/1,2,0,1;1,2,1,2;1,2,2,3;1,2.5,0,4;1,2.5,4,3/5", "70,0,0,0,-70/30,3,0,1;50,7,1,3;60,7,3,4;40,5,1,2;20,8,2,3;60,9,0,2;50,6,2,4/1080", }; foreach (string t in ss) { McfGraph g = new McfGraph(); string[] tt = t.Split('/'); string[] nn = tt[0].Split(','); string[] aa = tt[1].Split(';'); double cost, exp = double.Parse(tt[2]); foreach (string s in nn) g.AddNode(new McfNode(int.Parse(s))); foreach (string s in aa) { string[] a = s.Split(','); g.AddArc(new McfArc(int.Parse(a[0]), double.Parse(a[1])), int.Parse(a[2]), int.Parse(a[3]), true); } bool b = MinCostFlowProblem.Calc(g, out cost); Console.WriteLine("{0}, {1}", b, cost == exp); } } }