cp-library

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

View the Project on GitHub rniya/cp-library

:heavy_check_mark: Heavy Light Decomposition
(src/tree/HeavyLightDecomposition.hpp)

概要

木構造を分解する手法の一つで,部分木やパスに対する更新・取得処理を効率的に行うことができる.

以下に記す内容について,時間計算量は区間 $\lbrack l, r)$ に対する更新や総積の取得といった操作が 1 区間あたり $\mathrm{O}(\log n)$ で行えるとしたうえでの計算量である. また,各種クエリ操作についての関数において頂点についての情報を得たい際には引数の vertextrue にする必要がある(デフォルトでは false になっているので辺に情報が乗ったクエリ操作を扱うことになる).

空間計算量は $\mathrm{O}(n)$ である(ただし,定数倍は重い).

メンバ関数 効果 時間計算量
HeavyLightDecomposition(n) 頂点数 $n$ の木として初期化する. $\mathrm{O}(n)$
add_edge(u, v) 辺 $(u,v)$ を追加する. $\mathrm{O}(1)$
build() 追加された辺情報をもとに Heavy edge, Light edge の情報を構築する. $\mathrm{O}(n)$
idx(v) 内部処理における頂点 $v$ の識別子を返す.一点更新や一点取得の際に必要となる. $\mathrm{O}(1)$
la(v,k) $v$ から根の方向に $k$ 回辺を辿った先の頂点を返す. $\mathrm{O}(\log n)$
lca(u, v) 頂点 $u, v$ の最小共通祖先を返す. $\mathrm{O}(\log n)$
jump(s, t, i) 頂点 $s$ から $t$ への木上の最短路上で $s$ から距離 $i$ 離れた頂点を返す. $\mathrm{O}(\log n)$
distance(u, v) add_edge で追加された各辺の重みを 1 として,頂点 $u, v$ 間の距離を返す. $\mathrm{O}(\log n)$
query_path(u, v, f, vertex) 頂点 $u, v$ 間のパスに対してのクエリを処理する(演算が可換である必要がある). $\mathrm{O}(\log^2 n)$
query_path_noncommutative(u, v, f, vertex) 頂点 $u, v$ 間のパスに対してのクエリを処理する(下記注意参照). $\mathrm{O}(\log^2 n)$
query_subtree(u, f, vertex) 頂点 $u$ の部分木に対してのクエリを処理する. $\mathrm{O}(\log n)$
query_subtree(vs) 頂点集合 $\textit{vs}$ に対する auxiliary tree を構築し、その情報と根を返す。 $\vert \textit{vs} \vert = k$ として $\mathrm{O}(k (\log k + \log n))$

任意に根を取り dfs していき,現在見ている頂点とその子供の中で最も部分木のサイズが大きい頂点とを結ぶ edge を Heavy,それ以外を Light と分類する. このとき,任意の頂点について根との間のパス上の Light edge の本数を $\mathrm{O}(\log n)$ で抑えることができる(Light edge を辿るごとに,もとの木における部分木のサイズが半分未満になっていくため).

dfs において Heavy edge から優先的に探索していくようにすると,Heavy path 上の頂点群は dfs-order において連続するようになり,ある頂点からその先祖の頂点までのパスを dfs-order 上の $\mathrm{O}(\log n)$ 個の区間に分割することができる. 任意の 2 頂点間のパスは lowest common ancestor を経由することで $\mathrm{O}(\log n)$ 個の区間に分割でき,パスに対するクエリを他の列を扱うデータ構造と併せて効率的に処理することができる. また,1 頂点に対する操作は言うまでもなく,部分木に対する操作も任意の頂点の部分木内の頂点は dfs-order において 1 つの区間を成すことから容易に扱うことが可能である. 頂点及び辺のどちらについてのクエリにも対応しており,辺の場合は子孫側の頂点に値を載せるとして処理している.

注意

query_path_noncommutative を呼ぶ際には,付随するデータ構造もそれに対応しうるものである必要がある.具体的な要件としては,区間 $[l, r)$ に対して演算が左結合か右結合かを区別して正しく処理できなければならない.逆に言えば左結合か右結合かだけが重要なので,それぞれを処理する 2 つのデータ構造を用意することで解決することも可能である.

出題例

Verified with

Code

#pragma once
#include <algorithm>
#include <cassert>
#include <unordered_map>
#include <utility>
#include <vector>

struct HeavyLightDecomposition {
    std::vector<std::vector<int>> G;  // child of vertex v on heavy edge is G[v].front() if it is not parent of v
    int n, time;
    std::vector<int> par,  // parent of vertex v
        sub,               // size of subtree whose root is v
        dep,               // distance bitween root and vertex v
        head,              // vertex that is the nearest to root on heavy path of vertex v
        tree_id,           // id of tree vertex v belongs to
        vertex_id,         // id of vertex v (consecutive on heavy paths)
        vertex_id_inv;     // vertex_id_inv[vertex_id[v]] = v

    HeavyLightDecomposition() {}

    HeavyLightDecomposition(int n)
        : G(n),
          n(n),
          time(0),
          par(n, -1),
          sub(n),
          dep(n, 0),
          head(n),
          tree_id(n, -1),
          vertex_id(n, -1),
          vertex_id_inv(n) {}

    void add_edge(int u, int v) {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }

    void build(std::vector<int> roots = {0}) {
        int tree_id_cur = 0;
        for (int& r : roots) {
            assert(0 <= r and r < n);
            dfs_sz(r);
            head[r] = r;
            dfs_hld(r, tree_id_cur++);
        }
        assert(time == n);
        for (int v = 0; v < n; v++) vertex_id_inv[vertex_id[v]] = v;
    }

    int idx(int v) const { return vertex_id[v]; }

    int la(int v, int k) const {
        assert(0 <= v and v < n);
        assert(0 <= k and k <= dep[v]);
        while (1) {
            int u = head[v];
            if (vertex_id[v] - k >= vertex_id[u]) return vertex_id_inv[vertex_id[v] - k];
            k -= vertex_id[v] - vertex_id[u] + 1;
            v = par[u];
        }
    }

    int lca(int u, int v) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        for (;; v = par[head[v]]) {
            if (vertex_id[u] > vertex_id[v]) std::swap(u, v);
            if (head[u] == head[v]) return u;
        }
    }

    int jump(int s, int t, int i) const {
        assert(0 <= s and s < n);
        assert(0 <= t and t < n);
        assert(0 <= i);
        if (tree_id[s] != tree_id[t]) return -1;
        if (i == 0) return s;
        int p = lca(s, t), d = dep[s] + dep[t] - 2 * dep[p];
        if (d < i) return -1;
        if (dep[s] - dep[p] >= i) return la(s, i);
        return la(t, d - i);
    }

    int distance(int u, int v) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        return dep[u] + dep[v] - 2 * dep[lca(u, v)];
    }

    template <typename F> void query_path(int u, int v, const F& f, bool vertex = false) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        int p = lca(u, v);
        for (auto& e : ascend(u, p)) f(e.second, e.first + 1);
        if (vertex) f(vertex_id[p], vertex_id[p] + 1);
        for (auto& e : descend(p, v)) f(e.first, e.second + 1);
    }

    template <typename F> void query_path_noncommutative(int u, int v, const F& f, bool vertex = false) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        int p = lca(u, v);
        for (auto& e : ascend(u, p)) f(e.first + 1, e.second);
        if (vertex) f(vertex_id[p], vertex_id[p] + 1);
        for (auto& e : descend(p, v)) f(e.first, e.second + 1);
    }

    template <typename F> void query_subtree(int u, const F& f, bool vertex = false) const {
        assert(0 <= u and u < n);
        f(vertex_id[u] + !vertex, vertex_id[u] + sub[u]);
    }

    std::pair<std::unordered_map<int, std::vector<int>>, int> auxiliary_tree(std::vector<int> vs) {
        int len = vs.size();
        std::sort(vs.begin(), vs.end(), [&](int i, int j) { return vertex_id[i] < vertex_id[j]; });
        for (int i = 0; i + 1 < len; i++) vs.emplace_back(lca(vs[i], vs[i + 1]));
        std::sort(vs.begin(), vs.end(), [&](int i, int j) { return vertex_id[i] < vertex_id[j]; });
        vs.erase(std::unique(vs.begin(), vs.end()), vs.end());
        std::vector<int> st;
        std::unordered_map<int, std::vector<int>> res;
        for (int v : vs) {
            while (not st.empty() and vertex_id[st.back()] + sub[st.back()] <= vertex_id[v]) st.pop_back();
            if (not st.empty()) res[st.back()].emplace_back(v);
            st.emplace_back(v);
        }
        return {res, vs[0]};
    }

  private:
    void dfs_sz(int v) {
        sub[v] = 1;
        if (!G[v].empty() and G[v].front() == par[v]) std::swap(G[v].front(), G[v].back());
        for (int& u : G[v]) {
            if (u == par[v]) continue;
            par[u] = v;
            dep[u] = dep[v] + 1;
            dfs_sz(u);
            sub[v] += sub[u];
            if (sub[u] > sub[G[v].front()]) std::swap(u, G[v].front());
        }
    }

    void dfs_hld(int v, int tree_id_cur) {
        vertex_id[v] = time++;
        tree_id[v] = tree_id_cur;
        for (int& u : G[v]) {
            if (u == par[v]) continue;
            head[u] = (u == G[v][0] ? head[v] : u);
            dfs_hld(u, tree_id_cur);
        }
    }

    std::vector<std::pair<int, int>> ascend(int u, int v) const {  // [u, v), v is ancestor of u
        std::vector<std::pair<int, int>> res;
        while (head[u] != head[v]) {
            res.emplace_back(vertex_id[u], vertex_id[head[u]]);
            u = par[head[u]];
        }
        if (u != v) res.emplace_back(vertex_id[u], vertex_id[v] + 1);
        return res;
    }

    std::vector<std::pair<int, int>> descend(int u, int v) const {  // (u, v], u is ancestor of v
        if (u == v) return {};
        if (head[u] == head[v]) return {{vertex_id[u] + 1, vertex_id[v]}};
        auto res = descend(u, par[head[v]]);
        res.emplace_back(vertex_id[head[v]], vertex_id[v]);
        return res;
    }
};
#line 2 "src/tree/HeavyLightDecomposition.hpp"
#include <algorithm>
#include <cassert>
#include <unordered_map>
#include <utility>
#include <vector>

struct HeavyLightDecomposition {
    std::vector<std::vector<int>> G;  // child of vertex v on heavy edge is G[v].front() if it is not parent of v
    int n, time;
    std::vector<int> par,  // parent of vertex v
        sub,               // size of subtree whose root is v
        dep,               // distance bitween root and vertex v
        head,              // vertex that is the nearest to root on heavy path of vertex v
        tree_id,           // id of tree vertex v belongs to
        vertex_id,         // id of vertex v (consecutive on heavy paths)
        vertex_id_inv;     // vertex_id_inv[vertex_id[v]] = v

    HeavyLightDecomposition() {}

    HeavyLightDecomposition(int n)
        : G(n),
          n(n),
          time(0),
          par(n, -1),
          sub(n),
          dep(n, 0),
          head(n),
          tree_id(n, -1),
          vertex_id(n, -1),
          vertex_id_inv(n) {}

    void add_edge(int u, int v) {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        G[u].emplace_back(v);
        G[v].emplace_back(u);
    }

    void build(std::vector<int> roots = {0}) {
        int tree_id_cur = 0;
        for (int& r : roots) {
            assert(0 <= r and r < n);
            dfs_sz(r);
            head[r] = r;
            dfs_hld(r, tree_id_cur++);
        }
        assert(time == n);
        for (int v = 0; v < n; v++) vertex_id_inv[vertex_id[v]] = v;
    }

    int idx(int v) const { return vertex_id[v]; }

    int la(int v, int k) const {
        assert(0 <= v and v < n);
        assert(0 <= k and k <= dep[v]);
        while (1) {
            int u = head[v];
            if (vertex_id[v] - k >= vertex_id[u]) return vertex_id_inv[vertex_id[v] - k];
            k -= vertex_id[v] - vertex_id[u] + 1;
            v = par[u];
        }
    }

    int lca(int u, int v) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        for (;; v = par[head[v]]) {
            if (vertex_id[u] > vertex_id[v]) std::swap(u, v);
            if (head[u] == head[v]) return u;
        }
    }

    int jump(int s, int t, int i) const {
        assert(0 <= s and s < n);
        assert(0 <= t and t < n);
        assert(0 <= i);
        if (tree_id[s] != tree_id[t]) return -1;
        if (i == 0) return s;
        int p = lca(s, t), d = dep[s] + dep[t] - 2 * dep[p];
        if (d < i) return -1;
        if (dep[s] - dep[p] >= i) return la(s, i);
        return la(t, d - i);
    }

    int distance(int u, int v) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        return dep[u] + dep[v] - 2 * dep[lca(u, v)];
    }

    template <typename F> void query_path(int u, int v, const F& f, bool vertex = false) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        int p = lca(u, v);
        for (auto& e : ascend(u, p)) f(e.second, e.first + 1);
        if (vertex) f(vertex_id[p], vertex_id[p] + 1);
        for (auto& e : descend(p, v)) f(e.first, e.second + 1);
    }

    template <typename F> void query_path_noncommutative(int u, int v, const F& f, bool vertex = false) const {
        assert(0 <= u and u < n);
        assert(0 <= v and v < n);
        assert(tree_id[u] == tree_id[v]);
        int p = lca(u, v);
        for (auto& e : ascend(u, p)) f(e.first + 1, e.second);
        if (vertex) f(vertex_id[p], vertex_id[p] + 1);
        for (auto& e : descend(p, v)) f(e.first, e.second + 1);
    }

    template <typename F> void query_subtree(int u, const F& f, bool vertex = false) const {
        assert(0 <= u and u < n);
        f(vertex_id[u] + !vertex, vertex_id[u] + sub[u]);
    }

    std::pair<std::unordered_map<int, std::vector<int>>, int> auxiliary_tree(std::vector<int> vs) {
        int len = vs.size();
        std::sort(vs.begin(), vs.end(), [&](int i, int j) { return vertex_id[i] < vertex_id[j]; });
        for (int i = 0; i + 1 < len; i++) vs.emplace_back(lca(vs[i], vs[i + 1]));
        std::sort(vs.begin(), vs.end(), [&](int i, int j) { return vertex_id[i] < vertex_id[j]; });
        vs.erase(std::unique(vs.begin(), vs.end()), vs.end());
        std::vector<int> st;
        std::unordered_map<int, std::vector<int>> res;
        for (int v : vs) {
            while (not st.empty() and vertex_id[st.back()] + sub[st.back()] <= vertex_id[v]) st.pop_back();
            if (not st.empty()) res[st.back()].emplace_back(v);
            st.emplace_back(v);
        }
        return {res, vs[0]};
    }

  private:
    void dfs_sz(int v) {
        sub[v] = 1;
        if (!G[v].empty() and G[v].front() == par[v]) std::swap(G[v].front(), G[v].back());
        for (int& u : G[v]) {
            if (u == par[v]) continue;
            par[u] = v;
            dep[u] = dep[v] + 1;
            dfs_sz(u);
            sub[v] += sub[u];
            if (sub[u] > sub[G[v].front()]) std::swap(u, G[v].front());
        }
    }

    void dfs_hld(int v, int tree_id_cur) {
        vertex_id[v] = time++;
        tree_id[v] = tree_id_cur;
        for (int& u : G[v]) {
            if (u == par[v]) continue;
            head[u] = (u == G[v][0] ? head[v] : u);
            dfs_hld(u, tree_id_cur);
        }
    }

    std::vector<std::pair<int, int>> ascend(int u, int v) const {  // [u, v), v is ancestor of u
        std::vector<std::pair<int, int>> res;
        while (head[u] != head[v]) {
            res.emplace_back(vertex_id[u], vertex_id[head[u]]);
            u = par[head[u]];
        }
        if (u != v) res.emplace_back(vertex_id[u], vertex_id[v] + 1);
        return res;
    }

    std::vector<std::pair<int, int>> descend(int u, int v) const {  // (u, v], u is ancestor of v
        if (u == v) return {};
        if (head[u] == head[v]) return {{vertex_id[u] + 1, vertex_id[v]}};
        auto res = descend(u, par[head[v]]);
        res.emplace_back(vertex_id[head[v]], vertex_id[v]);
        return res;
    }
};
Back to top page