cplib-cpp

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

View the Project on GitHub hitonanode/cplib-cpp

:heavy_check_mark: (Weighted) matroid intersection ((重みつき)マトロイド交叉)
(combinatorial_opt/matroid_intersection.hpp)

マトロイド交叉(交差)問題 (matroid intersection)・共通独立集合問題とは,同じ台集合 $E$ を持つ二つのマトロイド $M_{1} = (E, \mathcal{I}_{1}), M_{2} = (E, \mathcal{I}_{2})$ が与えられたとき,$X \in \mathcal{I}_{1} \cap \mathcal{I}_{2}$ を満たす要素数最大の $X \subset E$ の一つを求めるもの.本問題は更に,重み関数 $f(e) : E \rightarrow \mathbb{R}$ が与えられたとき,要素数最大のもののうち特に $\sum_{e \in X} f(e)$ を最小化(最大化)するようなものを求める重みつき共通独立集合問題 (weighted matroid intersection problem) に一般化される.

本コードは,$n = |E|$,解となる集合の要素数の上界(例えば各マトロイドのランクの最小値)を $r$,マトロイドクラスのサーキットクエリ一回あたりの計算量を $c$ として,(重みなしの)マトロイド交叉を $O(nr(n + c))$ で求める.重みつきの場合は最短増加路を求めるパートが Bellman-Ford 法に置き換えられ,計算量は $O(nr(n^2 + c))$ となる(この計算量は例えば [2] のアルゴリズムを用いることで $O(nr(r + c + \log n))$ まで改善可能).

使用方法

weights を与えた場合,最小重み共通独立集合を求める.

UserDefinedMatroid m1, m2;
vector<int> weights(n);

assert(m1.size() == n);
assert(m2.size() == n);

std::vector<bool> maxindepset = MatroidIntersection(m1, m2, weights);

問題例

文献・リンク集

Depends on

Verified with

Code

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

// Find minimum weight augmenting path of matroid intersection.
// m1, m2: matroids
// I: independent set (will be updated if augmenting path is found)
//
// Return `true` iff augmenting path is found.
// Complexity: O(Cn + n^2) (C: circuit query)
template <class Matroid1, class Matroid2, class T = int>
bool matroid_intersection_augment(Matroid1 &m1, Matroid2 &m2, std::vector<bool> &I,
                                  const std::vector<T> &weights = {}) {
    const int n = m1.size();
    assert(m2.size() == n);
    assert((int)I.size() == n);

    auto weight = [&](int e) { return weights.empty() ? T() : weights.at(e) * (n + 1); };

    const int gs = n, gt = n + 1;
    shortest_path<T> sssp(n + 2);
    m1.set(I);
    m2.set(I);
    for (int e = 0; e < n; ++e) {
        if (I.at(e)) continue;
        auto c1 = m1.circuit(e), c2 = m2.circuit(e);
        if (c1.empty()) sssp.add_edge(e, gt, T());
        for (int f : c1) {
            if (f != e) sssp.add_edge(e, f, -weight(f) + T(1));
        }
        if (c2.empty()) sssp.add_edge(gs, e, weight(e) + T(1));
        for (int f : c2) {
            if (f != e) sssp.add_edge(f, e, weight(e) + T(1));
        }
    }
    sssp.solve(gs, gt);

    if (auto aug_path = sssp.retrieve_path(gt); aug_path.empty()) {
        return false;
    } else {
        for (auto e : aug_path) {
            if (e != gs and e != gt) I.at(e) = !I.at(e);
        }
        return true;
    }
}

// Minimum weight matroid intersection solver
// Algorithm based on http://dopal.cs.uec.ac.jp/okamotoy/lect/2015/matroid/
// Complexity: O(Cn^2 + n^3) (C : circuit query, non-weighted)
template <class Matroid1, class Matroid2, class T = int>
std::vector<bool>
MatroidIntersection(Matroid1 matroid1, Matroid2 matroid2, std::vector<T> weights = {}) {
    const int n = matroid1.size();
    assert(matroid2.size() == n);
    assert(weights.empty() or (int) weights.size() == n);

    std::vector<bool> I(n);

    if (weights.empty()) {
        matroid1.set(I);
        matroid2.set(I);
        for (int e = 0; e < n; ++e) {
            if (matroid1.circuit(e).empty() and matroid2.circuit(e).empty()) {
                I.at(e) = true;
                matroid1.set(I);
                matroid2.set(I);
            }
        }
    }

    while (matroid_intersection_augment(matroid1, matroid2, I, weights)) {}
    return I;
}
#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 5 "combinatorial_opt/matroid_intersection.hpp"

// Find minimum weight augmenting path of matroid intersection.
// m1, m2: matroids
// I: independent set (will be updated if augmenting path is found)
//
// Return `true` iff augmenting path is found.
// Complexity: O(Cn + n^2) (C: circuit query)
template <class Matroid1, class Matroid2, class T = int>
bool matroid_intersection_augment(Matroid1 &m1, Matroid2 &m2, std::vector<bool> &I,
                                  const std::vector<T> &weights = {}) {
    const int n = m1.size();
    assert(m2.size() == n);
    assert((int)I.size() == n);

    auto weight = [&](int e) { return weights.empty() ? T() : weights.at(e) * (n + 1); };

    const int gs = n, gt = n + 1;
    shortest_path<T> sssp(n + 2);
    m1.set(I);
    m2.set(I);
    for (int e = 0; e < n; ++e) {
        if (I.at(e)) continue;
        auto c1 = m1.circuit(e), c2 = m2.circuit(e);
        if (c1.empty()) sssp.add_edge(e, gt, T());
        for (int f : c1) {
            if (f != e) sssp.add_edge(e, f, -weight(f) + T(1));
        }
        if (c2.empty()) sssp.add_edge(gs, e, weight(e) + T(1));
        for (int f : c2) {
            if (f != e) sssp.add_edge(f, e, weight(e) + T(1));
        }
    }
    sssp.solve(gs, gt);

    if (auto aug_path = sssp.retrieve_path(gt); aug_path.empty()) {
        return false;
    } else {
        for (auto e : aug_path) {
            if (e != gs and e != gt) I.at(e) = !I.at(e);
        }
        return true;
    }
}

// Minimum weight matroid intersection solver
// Algorithm based on http://dopal.cs.uec.ac.jp/okamotoy/lect/2015/matroid/
// Complexity: O(Cn^2 + n^3) (C : circuit query, non-weighted)
template <class Matroid1, class Matroid2, class T = int>
std::vector<bool>
MatroidIntersection(Matroid1 matroid1, Matroid2 matroid2, std::vector<T> weights = {}) {
    const int n = matroid1.size();
    assert(matroid2.size() == n);
    assert(weights.empty() or (int) weights.size() == n);

    std::vector<bool> I(n);

    if (weights.empty()) {
        matroid1.set(I);
        matroid2.set(I);
        for (int e = 0; e < n; ++e) {
            if (matroid1.circuit(e).empty() and matroid2.circuit(e).empty()) {
                I.at(e) = true;
                matroid1.set(I);
                matroid2.set(I);
            }
        }
    }

    while (matroid_intersection_augment(matroid1, matroid2, I, weights)) {}
    return I;
}
Back to top page