out-of-kilter @ C++
コンテンツ
なぜ,
をしたかというと,仇打ちです.
当初,最小費用循環流を実装していた.
→できた
→検証しよう
→UVa 10594 時間オーバー,別の問題(2部グラフの最小費用最大マッチング)ではOKだった.
→別のアルゴリズムで UVa 10594を解く.
という流れ.はじめは,速度とか気にしてなかったのが原因か.
で本題.out-of-kilterは最小費用循環流を解くアルゴリズム.
といっても,最小費用流と最小費用循環流は簡単に変換できるから,最小費用流のアルゴリズムといっても良い.
とりあえず私の中のout-of-kilterのイメージは相補性条件が成り立つように,
負閉路消したり,ポテンシャル調整したりしていく.
なかなか,なんでこれで最適解が求まるか,傍から見ると不思議だったりしたアルゴリズム.
以前から実装してみたかったアルゴリズム.
そんなに複雑なことをしないので,実装はそこまで難しくないのだが,関数内関数が使えないのに,使おうとして,
酷いマクロを召喚している.
#include "graph.hpp" #include <stack> #include <queue> #define RC(_t, _h) (c(_t, _h) + p[_t] - p[_h]) // reduced cost #define R(_t, _h, _f) \ (_f ? \ RC(_t, _h) > 0 ? l(_t, _h) - f(_t, _h): u(_t, _h) - f(_t, _h): \ RC(_t, _h) < 0 ? f(_t, _h) - u(_t, _h): f(_t, _h) - l(_t, _h) \ ) #define EXIST_EDGE(_t, _h, _f) \ (_f ? \ (RC(_t, _h) > 0 && f(_t, _h) < l(_t, _h)) || (RC(_t, _h) <= 0 && f(_t, _h) < u(_t, _h)): \ (RC(_t, _h) < 0 && f(_t, _h) > u(_t, _h)) || (RC(_t, _h) >= 0 && f(_t, _h) > l(_t, _h)) \ ) #define IN_KILTER(_t, _h, _f) \ (_f ? \ RC(_t, _h) == 0 && l(_t, _h) <= f(_t, _h) && f(_t, _h) < u(_t, _h): \ RC(_t, _h) == 0 && l(_t, _h) < f(_t, _h) && f(_t, _h) <= u(_t, _h) \ ) #define OUT_OF_KILTER(_t, _h, _f) (EXIST_EDGE(_t, _h, _f) && !IN_KILTER(_t, _h, _f)) #define OUT_OF_KILTER_(_x) OUT_OF_KILTER(_x.first.first, _x.first.second, _x.second) #define FOR(x, xs) for (__typeof( (xs).begin() ) x = (xs).begin(); x != (xs).end(); x++) #define ABS(x) ((x) >= 0? (x): -(x)) template <class T> bool circulation(const Graph &g, // 単純有向グラフ const EdgeProperty<T> &c, // 費用 const EdgeProperty<T> &l, // 下限容量 const EdgeProperty<T> &u, // 上限容量 EdgeProperty<T> f // 最小費用循環流が記録される ) { const int n = g.succ.size(); T *p = new T[n]; fill_n(p, n, 0); // initialize potential for (Vertex v = 0; v < n; ++v) FOR (w, g.succ[v]) f(v, *w) = 0; // initialize flow typedef pair<Edge,bool> EB; stack<EB> out; // edges in out-of-kilter for (Vertex v = 0; v < n; ++v) { // intialize out-of-kilter FOR (w, g.succ[v]) { if (OUT_OF_KILTER(v, *w, true )) out.push(EB(Edge(v, *w), true)); if (OUT_OF_KILTER(v, *w, false)) out.push(EB(Edge(v, *w), false)); } } while (!out.empty()) { // choose an edge in out-of-kilter Edge e = out.top().first; bool forward = out.top().second; if(!OUT_OF_KILTER(e.first, e.second, forward)) { out.pop(); continue; } // find circuit which contains edge a, BFS from head to tail bool *fw = new bool[n]; T d = R(e.first, e.second, forward); Vertex t, h; // tail and head of Edge e. that is, tail -> head. if (forward) t = e.first, h = e.second; else t = e.second, h = e.first; queue<Vertex> q; Vertex* prev = new Vertex[n]; fill_n(prev, n, -1), prev[h] = h; q.push(h); while (!q.empty() && prev[t] < 0) { Vertex v = q.front(); q.pop(); FOR (w, g.succ[v]) if (EXIST_EDGE(v, *w, true)) q.push(*w), prev[*w] = v, fw[*w] = true, d = min(d, R(v, *w, fw[*w])); FOR (w, g.pred[v]) if (EXIST_EDGE(*w, v, false)) q.push(*w), prev[*w] = v, fw[*w] = false, d = min(d, R(*w, v, fw[*w])); } if (prev[t] >= 0) { // circuit is found for (Vertex v = t; v != prev[v]; v = prev[v]) // update flow if (fw[v]) f(prev[v], v) += d; else f(v, prev[v]) -= d; f(e) += forward? d: -d; continue; } // v is reachable from h <=> prev[v] >= 0 d = INF; stack<EB> stack; // candidate edge of out-of-kilter for (Vertex v = 0; v < n; ++v) if (prev[v] >= 0) { FOR (w, g.succ[v]) if (RC(v, *w) > 0 && prev[*w] < 0 && f(v, *w) <= u(v, *w)) d = min(d, RC(v, *w)), stack.push(EB(Edge(v, *w), false)); } else { FOR (w, g.succ[v]) if (RC(v, *w) < 0 && prev[*w] >= 0 && l(v, *w) <= f(v, *w)) d = min(d, -RC(v, *w)), stack.push(EB(Edge(v, *w), true)); } if (stack.empty()) return false; // there is no feasible circulation // update potential for (Vertex v = 0; v < n; ++v) if (prev[v] < 0) p[v] += d; // update out-of-kilter edges while(!stack.empty()) { if(OUT_OF_KILTER_(stack.top())) out.push(stack.top()); stack.pop(); } } return true; } int main() { Graph g(2); Edge e[2] = {g.add_edge(0, 1), g.add_edge(1, 0)}; EdgeProperty<int> c, l, u, f; c(e[0]) = 10; l(e[0]) = 0; u(e[0]) = 5; c(e[1]) = 10; l(e[1]) = 3; u(e[1]) = 5; circulation<int>(g, c, l, u, f); return 0; }
このままだと,計算量が費用関数に依存するがスケーリングという手法を用いると
費用関数に依存しない,強多項式時間のアルゴリズムが得られる.
らしい.雰囲気は分かるんだけど,細かいところが良く分からん.
作成者 Toru Mano
最終更新時刻 2023-01-01 (c70d5a1)