AOJ 2724 - Laser Cutter

解法

結論を言えば、最小重み二部マッチングに帰着できる。

線分の交点と端点が頂点になった有向グラフを考えると、考えるべき問題は「いくつか辺を追加することで、最小重みのオイラーグラフを作る」になる。
辺を追加するとしたら、線分の端点同士(終点 -> 始点) を考えるだけで良い。
実際、端点以外での交点は、入次数と出次数が等しくなるようにしかならないためである。
したがって、端点同士でマッチングを取れば良いことがわかる。
(一応言っておくと、交点を経由するのは回り道でしかないのでありえない。)

最小重み二部マッチングは最小費用流やハンガリアン法で解けて、O(N^3logN) か O(N^3) となる。
最小費用流で解くときは、重みが整数値じゃないので、ポテンシャルの判定式に eps をつけないとバグるから気をつけること。

ソースコード

#include <bits/stdc++.h>
using namespace std;

using pii = pair<int, int>;
constexpr double eps = 1e-10;

struct edge {
    int to, rev, cap;
    double cost;
    edge(int t, int c, double ct, int r) : to(t), rev(r), cap(c), cost(ct) {}
};
using graph = vector<vector<edge>>;

void add_edge(graph& g, int from, int to, int cap, double cost) {
    g[from].emplace_back(to, cap, cost, g[to].size());
    g[to].emplace_back(from, 0, -cost, g[from].size() - 1);
}

double min_cost_flow(graph& g, int s, int t, int f) {
    using P = pair<double, int>;
    const double inf = 1e18;
    double res = 0;
    vector<double > h(g.size()), dist(g.size());
    vector<int> prevv(g.size()), preve(g.size());
    while(f > 0) {
        priority_queue<P, vector<P>, greater<>> que;
        fill(begin(dist), end(dist), inf);
        dist[s] = 0;
        que.emplace(0, s);
        while(!que.empty()) {
            const auto cur_d = que.top().first;
            const int v = que.top().second;
            que.pop();
            if(dist[v] < cur_d) continue;
            for(int i = 0; i < (int)g[v].size(); ++i) {
                auto& e = g[v][i];
                if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to] + eps) {
                    dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
                    prevv[e.to] = v;
                    preve[e.to] = i;
                    que.emplace(dist[e.to], e.to);
                }
            }
        }
        if(dist[t] == inf) return -1;
        for(int v = 0; v < (int)g.size(); ++v) {
            h[v] += dist[v];
        }

        auto d = f;
        for(int v = t; v != s; v = prevv[v]) {
            d = min(d, g[prevv[v]][preve[v]].cap);
        }
        f -= d;
        res += d * h[t];
        for(int v = t; v != s; v = prevv[v]) {
            auto& e = g[prevv[v]][preve[v]];
            e.cap -= d;
            g[v][e.rev].cap += d;
        }
    }
    return res;
}

int main() {
    int n; cin >> n;
    int ix, iy; cin >> ix >> iy;
    vector<int> sx(n), sy(n), gx(n), gy(n);
    for(int i = 0; i < n; ++i) {
        cin >> sx[i] >> sy[i] >> gx[i] >> gy[i];
    }

    double ans = 0;
    for(int i = 0; i < n; ++i) {
        ans += hypot(gx[i] - sx[i], gy[i] - sy[i]);
    }
    graph g(n * 2 + 2);
    const int src = n * 2, sink = n * 2 + 1;
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < n; ++j) {
            add_edge(g, i, j + n, 1, hypot(sx[j] - gx[i], sy[j] - gy[i]));
        }
        add_edge(g, src, i, 1, 0);
        add_edge(g, i + n, sink, 1, 0);
    }
    ans += min_cost_flow(g, src, sink, n);

    cout << fixed << setprecision(10) << ans << endl;
}