cplib-cpp

This documentation is automatically generated by online-judge-tools/verification-helper

View the Project on GitHub hitonanode/cplib-cpp

:warning: Matroid union (マトロイドの合併)
(combinatorial_opt/matroid_union.hpp)

2つのマトロイド $M_{1} = (E, \mathcal{I}_{1}), M_{2} = (E, \mathcal{I}_{2})$, $\mathcal{I}_{1}$ に関して独立な集合 $I_1$, $\mathcal{I}_{2}$ に関して独立な集合 $I_2$ で $I_1 \cup I_2 = \emptyset$ を満たすものが与えられたとき,$I’_1 + I’_2 = I_1 + I_2 + \{ e \}$ を満たす新たな排反な独立集合 $I’_1, I’_2$ を見つけるアルゴリズム.特に重み最小の $e$ から貪欲に追加を試すことで,「合併したマトロイド」の最小重みサイズ $k$ 独立集合が $k = 1, 2, \dots$ について順次求められる.

これを応用すると,与えられた重み付き無向グラフについて,辺素な二つの全域木であって重みの総和が最小なものを見つけることも可能.

アルゴリズムの概要

$|E| = n$ として,$e = 1, \dots, n$ に $s$, $t$ を加えた $n + 2$ 頂点のグラフに次の要領で辺を張る:

このグラフで $s$ から $t$ への最短路を求め,$s$ の次に通った要素が新たに追加される($s$ から $t$ に到達不能ならば $I_1 \cup I_2$ は既に合併したマトロイド上の最大独立集合である).それ以降に通った要素は既に $I_1$ または $I_2$ に含まれているが,これらを集合間で出し入れすることで $I_1$ と $I_2$ の独立性が保たれる.

本コードには実装されていないが,3 個以上のマトロイドの合併に対しても同様のアルゴリズムが適用可能である.

使用方法

問題例

参考文献・リンク

Depends on

Code

#pragma once
#include "../graph/shortest_path.hpp"
#include <cassert>
#include <utility>
#include <vector>

// CUT begin
// Union matroid augment
// From I1, I2 (independent, exclusive),
// - find I1' and I2' s.t. |I1'| + |I2'| = |I1| + |I2| + 1 and return true
// - or return false
// Complexity: O(n(c + r)) (r: current rank, c: circuit query)
// Algorithm based on https://math.mit.edu/~goemans/18438F09/lec13.pdf
// Verified: CodeChef HAMEL
template <class M1, class M2, class State1, class State2, class T = int>
bool augment_union_matroid(M1 &matroid1, M2 &matroid2, State1 &I1, State2 &I2,
                           const std::vector<T> &weights) {
    const int M = matroid1.size();
    const int gs = M, gt = M + 1;
    shortest_path<T> sssp(M + 2);
    std::vector<int> color(M, -1);
    matroid1.set(I1);
    matroid2.set(I2);
    for (int e = 0; e < M; e++) {
        if (!I1[e] and !I2[e]) sssp.add_edge(gs, e, weights.size() ? weights[e] : 0);
        if (!I1[e]) {
            auto c = matroid1.circuit(e);
            if (c.empty()) sssp.add_edge(e, gt, 0), color[e] = 0;
            for (int f : c) {
                if (f != e) sssp.add_edge(e, f, 1);
            }
        }
        if (!I2[e]) {
            auto c = matroid2.circuit(e);
            if (c.empty()) sssp.add_edge(e, gt, 0), color[e] = 1;
            for (int f : c) {
                if (f != e) sssp.add_edge(e, f, 1);
            }
        }
    }
    sssp.solve(gs, gt);
    auto aug_path = sssp.retrieve_path(gt);
    if (aug_path.empty()) return false;
    assert(aug_path.size() >= 3);
    int c0 = -1;
    if (I1[aug_path[aug_path.size() - 2]]) c0 = 1;
    if (I2[aug_path[aug_path.size() - 2]]) c0 = 0;
    if (c0 < 0) c0 = color[aug_path[aug_path.size() - 2]];
    for (int k = int(aug_path.size()) - 2, e = aug_path[k]; k; e = aug_path[--k]) {
        (c0 ? I2 : I1)[e] = 1, (c0 ? I1 : I2)[e] = 0;
        c0 ^= 1;
    }
    return true;
}

// (Min weight) matroid partition
template <class M1, class M2, class T = int>
std::pair<std::vector<bool>, std::vector<bool>>
MinWeightMaxIndepSetInUnionMatroid(M1 mat1, M2 mat2, const std::vector<T> &weights = {}) {
    using State = std::vector<bool>;
    assert(mat1.size() == mat2.size());
    const int M = mat1.size();
    State I1(M), I2(M);
    while (augment_union_matroid(mat1, mat2, I1, I2, weights)) {}
    return {I1, I2};
}
#line 2 "graph/shortest_path.hpp"
#include <algorithm>
#include <cassert>
#include <deque>
#include <fstream>
#include <functional>
#include <limits>
#include <queue>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

template <typename T, T INF = std::numeric_limits<T>::max() / 2, int INVALID = -1>
struct shortest_path {
    int V, E;
    bool single_positive_weight;
    T wmin, wmax;

    std::vector<std::pair<int, T>> tos;
    std::vector<int> head;
    std::vector<std::tuple<int, int, T>> edges;

    void build_() {
        if (int(tos.size()) == E and int(head.size()) == V + 1) return;
        tos.resize(E);
        head.assign(V + 1, 0);
        for (const auto &e : edges) ++head[std::get<0>(e) + 1];
        for (int i = 0; i < V; ++i) head[i + 1] += head[i];
        auto cur = head;
        for (const auto &e : edges) {
            tos[cur[std::get<0>(e)]++] = std::make_pair(std::get<1>(e), std::get<2>(e));
        }
    }

    shortest_path(int V = 0) : V(V), E(0), single_positive_weight(true), wmin(0), wmax(0) {}
    void add_edge(int s, int t, T w) {
        assert(0 <= s and s < V);
        assert(0 <= t and t < V);
        edges.emplace_back(s, t, w);
        ++E;
        if (w > 0 and wmax > 0 and wmax != w) single_positive_weight = false;
        wmin = std::min(wmin, w);
        wmax = std::max(wmax, w);
    }

    void add_bi_edge(int u, int v, T w) {
        add_edge(u, v, w);
        add_edge(v, u, w);
    }

    std::vector<T> dist;
    std::vector<int> prev;

    // Dijkstra algorithm
    // - Requirement: wmin >= 0
    // - Complexity: O(E log E)
    using Pque = std::priority_queue<std::pair<T, int>, std::vector<std::pair<T, int>>,
                                     std::greater<std::pair<T, int>>>;
    template <class Heap = Pque> void dijkstra(int s, int t = INVALID) {
        assert(0 <= s and s < V);
        build_();
        dist.assign(V, INF);
        prev.assign(V, INVALID);
        dist[s] = 0;
        Heap pq;
        pq.emplace(0, s);
        while (!pq.empty()) {
            T d;
            int v;
            std::tie(d, v) = pq.top();
            pq.pop();
            if (t == v) return;
            if (dist[v] < d) continue;
            for (int e = head[v]; e < head[v + 1]; ++e) {
                const auto &nx = tos[e];
                T dnx = d + nx.second;
                if (dist[nx.first] > dnx) {
                    dist[nx.first] = dnx, prev[nx.first] = v;
                    pq.emplace(dnx, nx.first);
                }
            }
        }
    }

    // Dijkstra algorithm
    // - Requirement: wmin >= 0
    // - Complexity: O(V^2 + E)
    void dijkstra_vquad(int s, int t = INVALID) {
        assert(0 <= s and s < V);
        build_();
        dist.assign(V, INF);
        prev.assign(V, INVALID);
        dist[s] = 0;
        std::vector<char> fixed(V, false);
        while (true) {
            int r = INVALID;
            T dr = INF;
            for (int i = 0; i < V; i++) {
                if (!fixed[i] and dist[i] < dr) r = i, dr = dist[i];
            }
            if (r == INVALID or r == t) break;
            fixed[r] = true;
            int nxt;
            T dx;
            for (int e = head[r]; e < head[r + 1]; ++e) {
                std::tie(nxt, dx) = tos[e];
                if (dist[nxt] > dist[r] + dx) dist[nxt] = dist[r] + dx, prev[nxt] = r;
            }
        }
    }

    // Bellman-Ford algorithm
    // - Requirement: no negative loop
    // - Complexity: O(VE)
    bool bellman_ford(int s, int nb_loop) {
        assert(0 <= s and s < V);
        build_();
        dist.assign(V, INF), prev.assign(V, INVALID);
        dist[s] = 0;
        for (int l = 0; l < nb_loop; l++) {
            bool upd = false;
            for (int v = 0; v < V; v++) {
                if (dist[v] == INF) continue;
                for (int e = head[v]; e < head[v + 1]; ++e) {
                    const auto &nx = tos[e];
                    T dnx = dist[v] + nx.second;
                    if (dist[nx.first] > dnx) dist[nx.first] = dnx, prev[nx.first] = v, upd = true;
                }
            }
            if (!upd) return true;
        }
        return false;
    }

    // Bellman-ford algorithm using deque
    // - Requirement: no negative loop
    // - Complexity: O(VE)
    void spfa(int s) {
        assert(0 <= s and s < V);
        build_();
        dist.assign(V, INF);
        prev.assign(V, INVALID);
        dist[s] = 0;
        std::deque<int> q;
        std::vector<char> in_queue(V);
        q.push_back(s), in_queue[s] = 1;
        while (!q.empty()) {
            int now = q.front();
            q.pop_front(), in_queue[now] = 0;
            for (int e = head[now]; e < head[now + 1]; ++e) {
                const auto &nx = tos[e];
                T dnx = dist[now] + nx.second;
                int nxt = nx.first;
                if (dist[nxt] > dnx) {
                    dist[nxt] = dnx;
                    if (!in_queue[nxt]) {
                        if (q.size() and dnx < dist[q.front()]) { // Small label first optimization
                            q.push_front(nxt);
                        } else {
                            q.push_back(nxt);
                        }
                        prev[nxt] = now, in_queue[nxt] = 1;
                    }
                }
            }
        }
    }

    // 01-BFS
    // - Requirement: all weights must be 0 or w (positive constant).
    // - Complexity: O(V + E)
    void zero_one_bfs(int s, int t = INVALID) {
        assert(0 <= s and s < V);
        build_();
        dist.assign(V, INF), prev.assign(V, INVALID);
        dist[s] = 0;
        std::vector<int> q(V * 4);
        int ql = V * 2, qr = V * 2;
        q[qr++] = s;
        while (ql < qr) {
            int v = q[ql++];
            if (v == t) return;
            for (int e = head[v]; e < head[v + 1]; ++e) {
                const auto &nx = tos[e];
                T dnx = dist[v] + nx.second;
                if (dist[nx.first] > dnx) {
                    dist[nx.first] = dnx, prev[nx.first] = v;
                    if (nx.second) {
                        q[qr++] = nx.first;
                    } else {
                        q[--ql] = nx.first;
                    }
                }
            }
        }
    }

    // Dial's algorithm
    // - Requirement: wmin >= 0
    // - Complexity: O(wmax * V + E)
    void dial(int s, int t = INVALID) {
        assert(0 <= s and s < V);
        build_();
        dist.assign(V, INF), prev.assign(V, INVALID);
        dist[s] = 0;
        std::vector<std::vector<std::pair<int, T>>> q(wmax + 1);
        q[0].emplace_back(s, dist[s]);
        int ninq = 1;

        int cur = 0;
        T dcur = 0;
        for (; ninq; ++cur, ++dcur) {
            if (cur == wmax + 1) cur = 0;
            while (!q[cur].empty()) {
                int v = q[cur].back().first;
                T dnow = q[cur].back().second;
                q[cur].pop_back(), --ninq;
                if (v == t) return;
                if (dist[v] < dnow) continue;

                for (int e = head[v]; e < head[v + 1]; ++e) {
                    const auto &nx = tos[e];
                    T dnx = dist[v] + nx.second;
                    if (dist[nx.first] > dnx) {
                        dist[nx.first] = dnx, prev[nx.first] = v;
                        int nxtcur = cur + int(nx.second);
                        if (nxtcur >= int(q.size())) nxtcur -= q.size();
                        q[nxtcur].emplace_back(nx.first, dnx), ++ninq;
                    }
                }
            }
        }
    }

    // Solver for DAG
    // - Requirement: graph is DAG
    // - Complexity: O(V + E)
    bool dag_solver(int s) {
        assert(0 <= s and s < V);
        build_();
        dist.assign(V, INF), prev.assign(V, INVALID);
        dist[s] = 0;
        std::vector<int> indeg(V, 0);
        std::vector<int> q(V * 2);
        int ql = 0, qr = 0;
        q[qr++] = s;
        while (ql < qr) {
            int now = q[ql++];
            for (int e = head[now]; e < head[now + 1]; ++e) {
                const auto &nx = tos[e];
                ++indeg[nx.first];
                if (indeg[nx.first] == 1) q[qr++] = nx.first;
            }
        }
        ql = qr = 0;
        q[qr++] = s;
        while (ql < qr) {
            int now = q[ql++];
            for (int e = head[now]; e < head[now + 1]; ++e) {
                const auto &nx = tos[e];
                --indeg[nx.first];
                if (dist[nx.first] > dist[now] + nx.second)
                    dist[nx.first] = dist[now] + nx.second, prev[nx.first] = now;
                if (indeg[nx.first] == 0) q[qr++] = nx.first;
            }
        }
        return *max_element(indeg.begin(), indeg.end()) == 0;
    }

    // Retrieve a sequence of vertex ids that represents shortest path [s, ..., goal]
    // If not reachable to goal, return {}
    std::vector<int> retrieve_path(int goal) const {
        assert(int(prev.size()) == V);
        assert(0 <= goal and goal < V);
        if (dist[goal] == INF) return {};
        std::vector<int> ret{goal};
        while (prev[goal] != INVALID) {
            goal = prev[goal];
            ret.push_back(goal);
        }
        std::reverse(ret.begin(), ret.end());
        return ret;
    }

    void solve(int s, int t = INVALID) {
        if (wmin >= 0) {
            if (single_positive_weight) {
                zero_one_bfs(s, t);
            } else if (wmax <= 10) {
                dial(s, t);
            } else {
                if ((long long)V * V < (E << 4)) {
                    dijkstra_vquad(s, t);
                } else {
                    dijkstra(s, t);
                }
            }
        } else {
            bellman_ford(s, V);
        }
    }

    // Warshall-Floyd algorithm
    // - Requirement: no negative loop
    // - Complexity: O(E + V^3)
    std::vector<std::vector<T>> floyd_warshall() {
        build_();
        std::vector<std::vector<T>> dist2d(V, std::vector<T>(V, INF));
        for (int i = 0; i < V; i++) {
            dist2d[i][i] = 0;
            for (const auto &e : edges) {
                int s = std::get<0>(e), t = std::get<1>(e);
                dist2d[s][t] = std::min(dist2d[s][t], std::get<2>(e));
            }
        }
        for (int k = 0; k < V; k++) {
            for (int i = 0; i < V; i++) {
                if (dist2d[i][k] == INF) continue;
                for (int j = 0; j < V; j++) {
                    if (dist2d[k][j] == INF) continue;
                    dist2d[i][j] = std::min(dist2d[i][j], dist2d[i][k] + dist2d[k][j]);
                }
            }
        }
        return dist2d;
    }

    void to_dot(std::string filename = "shortest_path") const {
        std::ofstream ss(filename + ".DOT");
        ss << "digraph{\n";
        build_();
        for (int i = 0; i < V; i++) {
            for (int e = head[i]; e < head[i + 1]; ++e) {
                ss << i << "->" << tos[e].first << "[label=" << tos[e].second << "];\n";
            }
        }
        ss << "}\n";
        ss.close();
        return;
    }
};
#line 6 "combinatorial_opt/matroid_union.hpp"

// CUT begin
// Union matroid augment
// From I1, I2 (independent, exclusive),
// - find I1' and I2' s.t. |I1'| + |I2'| = |I1| + |I2| + 1 and return true
// - or return false
// Complexity: O(n(c + r)) (r: current rank, c: circuit query)
// Algorithm based on https://math.mit.edu/~goemans/18438F09/lec13.pdf
// Verified: CodeChef HAMEL
template <class M1, class M2, class State1, class State2, class T = int>
bool augment_union_matroid(M1 &matroid1, M2 &matroid2, State1 &I1, State2 &I2,
                           const std::vector<T> &weights) {
    const int M = matroid1.size();
    const int gs = M, gt = M + 1;
    shortest_path<T> sssp(M + 2);
    std::vector<int> color(M, -1);
    matroid1.set(I1);
    matroid2.set(I2);
    for (int e = 0; e < M; e++) {
        if (!I1[e] and !I2[e]) sssp.add_edge(gs, e, weights.size() ? weights[e] : 0);
        if (!I1[e]) {
            auto c = matroid1.circuit(e);
            if (c.empty()) sssp.add_edge(e, gt, 0), color[e] = 0;
            for (int f : c) {
                if (f != e) sssp.add_edge(e, f, 1);
            }
        }
        if (!I2[e]) {
            auto c = matroid2.circuit(e);
            if (c.empty()) sssp.add_edge(e, gt, 0), color[e] = 1;
            for (int f : c) {
                if (f != e) sssp.add_edge(e, f, 1);
            }
        }
    }
    sssp.solve(gs, gt);
    auto aug_path = sssp.retrieve_path(gt);
    if (aug_path.empty()) return false;
    assert(aug_path.size() >= 3);
    int c0 = -1;
    if (I1[aug_path[aug_path.size() - 2]]) c0 = 1;
    if (I2[aug_path[aug_path.size() - 2]]) c0 = 0;
    if (c0 < 0) c0 = color[aug_path[aug_path.size() - 2]];
    for (int k = int(aug_path.size()) - 2, e = aug_path[k]; k; e = aug_path[--k]) {
        (c0 ? I2 : I1)[e] = 1, (c0 ? I1 : I2)[e] = 0;
        c0 ^= 1;
    }
    return true;
}

// (Min weight) matroid partition
template <class M1, class M2, class T = int>
std::pair<std::vector<bool>, std::vector<bool>>
MinWeightMaxIndepSetInUnionMatroid(M1 mat1, M2 mat2, const std::vector<T> &weights = {}) {
    using State = std::vector<bool>;
    assert(mat1.size() == mat2.size());
    const int M = mat1.size();
    State I1(M), I2(M);
    while (augment_union_matroid(mat1, mat2, I1, I2, weights)) {}
    return {I1, I2};
}
Back to top page