图论选题

===

Index

被最多点到达的节点集合

hdu3639

n个点,m条有向边的图,定义节点u的得分为所有不等于u且能通过某条路径到达节点u的节点数目,求节点的最大得分,以及拥有最大得分的节点集合。

  • 2 <= n <= 5000
  • 1 <= m <= 30000

分析

首先进行强联通分量缩点,缩点后建反向图,在该拓扑图上求每个节点能到达的节点数目即可。

void solve() {
    int n, m;
    cin >> n >> m;
    SCC c(n);
    vector<array<int, 2>> es(m);
    for (int i = 0, u, v; i < m; ++i) {
        cin >> u >> v;
        c.add_edge(u, v);
        es[i] = {u, v};
    }
    auto p = c.scc();
    n = c.cnt;
    vector<vector<int>> g(n);

    vector<int> f(n);
    for (int i = 0; i < n; ++i) {
        f[i] = p[i].size() - 1;
    }

    for (auto &e : es) {
        int u = c.ids[e[0]], v = c.ids[e[1]];
        if (u != v) {
            g[v].push_back(u);
        }
    }
    vector<int> vis(n);

    function<void(int, int)> dfs = [&](int i, int u) {
        vis[u] = 1;
        if (u != i) f[i] += p[u].size();
        for (int v : g[u]) {
            if (!vis[v]) dfs(i, v);
        }
    };
    for (int i = 0; i < n; i++) {
        if (c.in[i] == 0) continue;
        vis.assign(n, 0);
        dfs(i, i);
    }

    int mx = 0;
    for (int i = 0; i < n; ++i) {
        if (f[i] > mx) mx = f[i];
    }

    cout << mx << '\n';

    vector<int> ans;

    for (int i = 0; i < n; ++i) {
        if (f[i] == mx) {
            for (auto &x : p[i]) 
                ans.push_back(x);
        }   
    }
    sort(ans.begin(), ans.end());
    for (int i = 0, n = ans.size(); i < n; ++i) {
        cout << ans[i] << " \n"[i == n - 1];
    }
}

最小树形图模板

minCost Arborescence

给定带权有向图G,求以节点s为根的最小树形图,该树以s为根,包含所有n个节点,树中的有向边在原图中都存在且总边权和最小。 输出总权和S,以及n个数,第i个数为第i个节点的父节点编号。

  • 1 <= n <= 2e5
  • n - 1 <= m <= 2e5
  • 1 <= w <= 1e9

Chu-liu算法

时间复杂度 O(m*log(n))

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

// https://judge.yosupo.jp/problem/directedmst

struct rollback_dsu {
    explicit rollback_dsu(int n) : n(n), pa_or_siz(n, -1) {}
    int get(int u) const { return pa_or_siz[u] < 0 ? u : get(pa_or_siz[u]);}
    bool merge(int u, int v) {
        assert(0 <= u && u < n && 0 <= v && v < n);
        u = get(u), v = get(v);
        if (u == v) return false;
        if (-pa_or_siz[u] < -pa_or_siz[v]) std::swap(u, v);
        history.emplace(v, pa_or_siz[v]);
        pa_or_siz[u] += pa_or_siz[v];
        pa_or_siz[v] = u;
        return true;
    }
    int size(int u) const {
        assert(0 <= u && u < n);
        return -pa_or_siz[get(u)];
    }
    void rollback() {
        assert(!history.empty());
        auto [v, val] = history.top();
        auto u = pa_or_siz[v];
        pa_or_siz[v] = val;
        pa_or_siz[u] -= val;
        history.pop();
    }
    void rollback(int count) {
        for (auto i = 0; i < count; ++i) 
            rollback();
    }

private:
    int n;
    vector<int> pa_or_siz;
    stack<std::pair<int, int>> history;
};

template <typename Cost> struct directed_mst {
    explicit directed_mst(int _n) : n(_n), heap_(_n, -1) {}

    void add_edge(int from, int to, Cost cost) {
        assert(0 <= from && from < n && 0 <= to && to < n);
        auto id = static_cast<int>(from_.size());
        from_.push_back(from); to_.push_back(to); cost_.push_back(cost);
        left_.push_back(-1); right_.push_back(-1); lazy_.push_back(Cost{});
        heap_[to] = merge(heap_[to], id);
    }

    pair<Cost, vector<int>> get(int root, bool build_solution = false) {
        rollback_dsu dsu(n);
        Cost result{};
        vector<int> seen(n, -1), path(n), q(n), in(n, -1);
        seen[root] = root;
        vector<std::pair<int, std::vector<int>>> cycles;
        for (auto s = 0; s < n; ++s) {
            auto u = s, pos = 0, w = -1;
            while (!~seen[u]) {
                if (!~heap_[u]) return {-1, {}};
                push(heap_[u]);
                auto e = heap_[u];
                result += cost_[e];
                lazy_[heap_[u]] -= cost_[e];
                heap_[u] = pop(heap_[u]);
                q[pos] = e;
                path[pos++] = u;
                seen[u] = s;
                u = dsu.get(from_[e]);
                if (seen[u] == s) {
                    auto cycle = -1;
                    auto end = pos;
                    do {
                        w = path[--pos];
                        cycle = merge(cycle, heap_[w]);
                    } while (dsu.merge(u, w));
                    u = dsu.get(u);
                    heap_[u] = cycle;
                    seen[u] = -1;
                    cycles.emplace_back(u,vector<int>(q.begin() + pos,q.begin() + end));
                }
            }
            for (auto i = 0; i < pos; ++i) 
                in[dsu.get(to_[q[i]])] = q[i];
        }
        vector<int> parent;
        if (build_solution) {
            for (auto it = cycles.rbegin(); it != cycles.rend(); ++it) {
                auto &[u, comp] = *it;
                auto count = static_cast<int>(comp.size()) - 1;
                dsu.rollback(count);
                auto inedge = in[u];
                for (auto e : comp) {
                    in[dsu.get(to_[e])] = e;
                }
                in[dsu.get(to_[inedge])] = inedge;
            }
            parent.reserve(n);
            for (auto i : in) {
                parent.push_back(~i ? from_[i] : -1);
            }
        }
        return {result, parent};
    }

private:
    void push(int u) {
        cost_[u] += lazy_[u];
        if (~left_[u]) lazy_[left_[u]] += lazy_[u];
        if (~right_[u]) lazy_[right_[u]] += lazy_[u];
        lazy_[u] = 0;
    }
    int merge(int u, int v) {
        if (!~u || !~v) return ~u ? u : v;
        push(u); push(v);
        if (cost_[u] > cost_[v]) swap(u, v);
        right_[u] = merge(v, right_[u]);
        std::swap(left_[u], right_[u]);
        return u;
    }
    int pop(int u) {
        push(u);
        return merge(left_[u], right_[u]);
    }
    const int n;
    vector<int> from_, to_, left_, right_, heap_;
    vector<Cost> cost_, lazy_;
};


int main() {
    cin.tie(0)->sync_with_stdio(0);
    int N, M, S;
    cin >> N >> M >> S;
    directed_mst<long long> dmst(N);
    for (auto i = 0; i < M; ++i) {
        int a, b, c;
        cin >> a >> b >> c;
        dmst.add_edge(a, b, c);
    }
    auto [X, parent] = dmst.get(S, true);
    parent[S] = S;
    cout << X << '\n';
    for (auto p : parent) {
        cout << p << ' ';
    }
}

虚根最小树形图

hdu 4009

给定X,Y,Z, n个村庄,每个村庄有个三维坐标(x,y,z),可以在每个村庄建设水井,花费 z * X,也可在村庄间建设水管,从村庄a修建到村庄b的代价为: 如果a的高度不低于b的高度,代价为 Y * Manhattan(a,b), 否则为 Z + Y * Manhattan(a,b). 另外每个村庄只能修建到部分村庄的水管,求让所有村庄都能喝上水的最少代价。

  • 1 <= n <= 1000
  • 1 <= x, y, z <= 1000

分析

建立一个虚拟源点,与所有点连边,边权为在该点建设水井的代价,点与点之间建边,从源点计算最小树形图即可,注意剪枝(这个模板第一次提交T了,剪掉连边大于在该处新建一个水井的边,快了很多)。

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n, X, Y, Z, k, x;
    while (cin >> n >> X >> Y >> Z ) {
        if (!n) break;
        directed_mst<long long> d(n + 1);
        vector<array<int, 3>> ps(n + 1);
        for (int i = 1; i <= n; ++i) {
            cin >> ps[i][0] >> ps[i][1] >> ps[i][2];
        } 
        auto dis = [&](int i, int j) {
            long long d = abs(ps[i][0] - ps[j][0]) + abs(ps[i][1] - ps[j][1]) + abs(ps[i][2] - ps[j][2]);
            d *= Y;
            if (ps[i][2] < ps[j][2]) d += Z;
            return d;
        };

        for (int i = 1; i <= n; ++i) {
            d.add_edge(0, i, ps[i][2] * 1ll * X);
            cin >> k;
            for (int j = 0; j < k; ++j) {
                cin >> x;
                auto ds = dis(i, x);
                if(i != x && ds < ps[x][2] * X) d.add_edge(i, x, ds); // 剪枝
            }
        }

        cout << d.get(0).first << '\n';      
    }
    return 0;
}

支配树

对于一个有向图,起点为s,对于图中的每个点w,都存在点d满足去掉d之后从s无法到达w,称作 d支配w,d是w的一个支配点。

支配w的点可以有多个,但是至少会有一个。显然,对于起点以外的点,它们都有两个平凡的支配点,一个是自己,一个是起点。

在支配w的点中,如果一个支配点i!=w 满足i被w剩下的所有非平凡支配点支配,则这个i称作w 的最近支配点(immediate dominator),记作 idom(w)。

定理1

我们把图的起点称作s,除s以外每个点均存在唯一的idom。

支配树

连上所有s以外的idom(w) -> w的边,能得到一棵树,其中每个点支配它子树中的所有点,它就是支配树

dominator tree

给定n个点,m条边的有向图,计算以s为根节点的支配树。输出n个数,第i个数为节点i的父节点编号,其中s节点输出s,如果从s无法到达节点i,输出-1.

  • 1 <= n, m <= 2e5
struct dominator_tree {
    explicit dominator_tree(int n)
        : n(n), fa(n), idom(n, -1), sdom(n, -1), dsu(n), label(n), g(n), gr(n) {
        order_.reserve(n);
        iota(dsu.begin(), dsu.end(), 0);
        iota(label.begin(), label.end(), 0);
    }
    void add_edge(int from, int to) {
        assert(0 <= from && from < n && 0 <= to && to < n);
        g[from].push_back(to);
        gr[to].push_back(from);
    }
    void get(int root) {
        assert(0 <= root && root < n);
        dfs(root);
        vector<vector<int>> bucket(n);
        vector<int> x(n);
        for (auto i = static_cast<int>(order_.size()) - 1; i >= 0; --i) {
            auto u = order_[i];
            for (auto v : gr[u]) {
                if (~sdom[v]) sdom[u] = std::min(sdom[u], sdom[eval(v)]);
            }
            bucket[order_[sdom[u]]].push_back(u);
            for (auto v : bucket[fa[u]]) 
                x[v] = eval(v);
            bucket[fa[u]].clear();
            link(fa[u], u);
        }
        for (auto i = 1; i < static_cast<int>(order_.size()); ++i) {
            auto u = order_[i], v = x[u];
            idom[u] = (sdom[u] == sdom[v] ? sdom[u] : idom[v]);
        }
        for (auto i = 1; i < static_cast<int>(order_.size()); ++i) {
            auto u = order_[i];
            idom[u] = order_[idom[u]];
        }
        idom[root] = root;
    }
    int operator[](int u) const {
        assert(0 <= u && u < n);
        return idom[u];
    }

private:
    void dfs(int u) {
        sdom[u] = static_cast<int>(order_.size());
        order_.push_back(u);
        for (auto v : g[u]) if (!~sdom[v]) {
            fa[v] = u;
            dfs(v);
        }
    }
    int find(int u) {
        if (dsu[u] == u) return u;
        auto root = find(dsu[u]);
        if (sdom[label[u]] > sdom[label[dsu[u]]]) label[u] = label[dsu[u]];
        return dsu[u] = root;
    }
    int eval(int u) {
        find(u);
        return label[u];
    }
    void link(int u, int v) { dsu[v] = u; }
    int n;
    vector<int> order_, fa, idom, sdom, dsu, label;
    vector<vector<int>> g, gr;
};

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    std::cin.tie(0)->sync_with_stdio(0);
    int N, M, S;
    std::cin >> N >> M >> S;
    dominator_tree dt(N);
    for (auto i = 0; i < M; ++i) {
        int a, b;
        std::cin >> a >> b;
        dt.add_edge(a, b);
    }
    dt.get(S);
    for (auto i = 0; i < N; ++i) {
        std::cout << dt[i] << ' ';
    }
    return 0;
}

曼哈顿生成树

manhattan mst

给定二维平面上n个点,任意两个点增加一条长为曼哈顿距离的无向边,求图中的最小生成树。

  • 1 <= n <= 2e5
  • 0 <= x, y <= 1e9
struct DSU {
    vector<int> p, siz;
    DSU(int n) : p(n), siz(n, 1) { iota(p.begin(), p.end(), 0); }
    inline int get(int x) { return (x == p[x] ? x : (p[x] = get(p[x])));}
    bool same(int x, int y) { return get(x) == get(y); }
    bool merge(int x, int y) {
        x = get(x), y = get(y);
        if (x == y) return false;
        siz[x] += siz[y];
        p[y] = x;
        return true;
    }
    int size(int x) { return siz[get(x)]; }
    vector<vector<int>> groups() {
        vector<vector<int>> res(p.size());
        for (int i = 0; i < p.size(); i++) res[get(i)].push_back(i);
        res.erase(
            remove_if(res.begin(), res.end(),
                           [&](const vector<int>& v) { return v.empty(); }),
            res.end());
        return res;
    }
};

template <typename T, typename Comp>
struct KruscalMST {
    KruscalMST() : KruscalMST(0) {}
    explicit KruscalMST(const int n) : _n(n) {}

    void add_edge(const int u, const int v, const T& cost) { _built = false; es.emplace_back(u, v, cost);}
    void add_edge(const tuple<int, int, T>& e) { _built = false; es.push_back(e);}
    bool build() {
        _built = true, _weight_sum = 0;
        if (_n == 0) return true;
        DSU uf(_n);
        sort(es.begin(), es.end(), [this](const auto& u, const auto& v) { return _comp(std::get<2>(u), std::get<2>(v));});
        for (auto& [u, v, w] : es) {
            if (uf.same(u, v)) u = v = _n;
            else { uf.merge(u, v); _weight_sum += w;}
        }
        es.erase(std::remove_if(es.begin(), es.end(), [this](auto& e) { return std::get<0>(e) == _n; }), es.end());
        return int(es.size()) == _n - 1;
    }
    T get_weight() const { assert(_built); return _weight_sum;}
    const std::vector<tuple<int, int, T>>& get_mst() const { assert(_built); return es;}
private:
    int _n;
    T _weight_sum;
    Comp _comp{};
    vector<tuple<int, int, T>> es;
    bool _built = false;
};
template <typename T> using minMst = KruscalMST<T, less<T>>;
template <typename T> using maxMst = KruscalMST<T, greater<T>>;

template <typename WeightType, typename T>
minMst<WeightType> manhattan_mst(vector<pair<T, T>> points) {
    const int n = points.size();
    vector<int> p(n);
    iota(p.begin(), p.end(), 0);

    auto makees = [&](minMst<WeightType> &mst) {
        std::sort(
            p.begin(), p.end(),
            [&points](int i, int j) {
                const auto &[xi, yi] = points[i];
                const auto &[xj, yj] = points[j];
                return yi - xi == yj - xj ? xi < xj : yi - xi < yj - xj;
            }
        );

        vector<T> comp_x(n);
        for (int i = 0; i < n; ++i) comp_x[i] = points[i].first;
        sort(comp_x.begin(), comp_x.end());
        comp_x.erase(unique(comp_x.begin(), comp_x.end()), comp_x.end());
        const int m = comp_x.size();

        auto get = [&](const T& x) { return lower_bound(comp_x.begin(), comp_x.end(), x) - comp_x.begin();};

        struct PrefixMaxQuery {
            const std::pair<T, int> _neg_inf{ std::numeric_limits<T>::min(), -1 };
            int _n;
            std::vector<std::pair<T, int>> _dat;
            PrefixMaxQuery(int n) : _n(n), _dat(_n + 1, _neg_inf) {}
            void chmax(int i, const std::pair<T, int>& val) {
                for (++i; i <= _n; i += -i & i) if (_dat[i].first < val.first) _dat[i] = val;
            }
            std::pair<T, int> prefix_max(int r) const {
                std::pair<T, int> res = _neg_inf;
                for (; r; r -= -r & r) if (res.first < _dat[r].first) res = _dat[r];
                return res;
            }
        } pmq{ m };

        for (int i : p) {
            const auto& [x, y] = points[i];
            const int cx = get(x);
            if (const auto p = pmq.prefix_max(cx + 1); p != pmq._neg_inf) {
                const auto& [v, j] = p;
                mst.add_edge(i, j, x + y - v);
            }
            pmq.chmax(cx, { x + y, i });
        }
    };

    minMst<WeightType> mst(n);
    for (int x_rev = 0; x_rev < 2; ++x_rev) {
        for (int y_rev = 0; y_rev < 2; ++y_rev) {
            for (int xy_rev = 0; xy_rev < 2; ++xy_rev) {
                makees(mst);
                for (auto& [x, y] : points) std::swap(x, y);
            }
            for (auto& [x, _] : points) x = -x;
        }
        for (auto& [_, y] : points) y = -y;
    }
    assert(mst.build());
    return mst;
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    
    int n;
    std::cin >> n;

    std::vector<std::pair<int, int>> points(n);
    for (auto &[x, y] : points) std::cin >> x >> y;

    auto mst = manhattan_mst<long long>(points);
    std::cout << mst.get_weight() << '\n';
    for (auto [i, j, _] : mst.get_mst()) {
        std::cout << i << ' ' << j << '\n';
    }

    return 0;
}

打赏一下

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦