Skip to content

tree_contour.hpp

SECTIONGraph INCLUDEnoya/tree_contour.hpp

Centroid-decomposition distance-range updates and queries on a static tree.

Verified by vertex_add_range_contour_sum_on_tree, vertex_get_range_contour_add_on_tree.

在静态树上按到某点的距离区间做更新或查询;适合“距离在 [l,r) 内的所有点”这类操作。

Implementation

View on GitHub

#ifndef NOYA_TREE_CONTOUR_HPP
#define NOYA_TREE_CONTOUR_HPP 1

/// @complexity Time: O(n log^2 n) construction and O(log^2 n) per update or
/// query. Space: O(n log n).

#include <algorithm>
#include <cassert>
#include <tuple>
#include <utility>
#include <vector>

namespace noya {

/// @brief Maintain values indexed by tree distance. Every vertex is recorded
/// in all centroid-ancestor buckets; querying adds all buckets and subtracts
/// the bucket of the branch containing the query vertex. Fenwick trees over
/// distances support both point-add/range-sum and the dual range-add/point-get.
class tree_contour {
  struct fenwick {
    std::vector<long long> data;

    fenwick() = default;
    explicit fenwick(int size) : data(size + 1) {}

    int size() const { return int(data.size()) - 1; }

    void add(int index, long long value) {
      assert(0 <= index && index < size());
      for (index++; index < int(data.size()); index += index & -index) {
        data[index] += value;
      }
    }

    long long prefix_sum(int count) const {
      count = std::clamp(count, 0, size());
      long long result = 0;
      for (; count > 0; count -= count & -count) {
        result += data[count];
      }
      return result;
    }
  };

  struct bucket {
    int distance_count = 0;
    fenwick point_values;
    fenwick range_tags;

    void reset(int count) {
      distance_count = count;
      point_values = fenwick(count);
      range_tags = fenwick(count + 1);
    }

    void point_add(int distance, long long value) {
      point_values.add(distance, value);
    }

    long long point_prefix(int distance_bound) const {
      return point_values.prefix_sum(distance_bound);
    }

    void range_add(int left, int right, long long value) {
      left = std::clamp(left, 0, distance_count);
      right = std::clamp(right, 0, distance_count);
      if (left >= right) {
        return;
      }
      range_tags.add(left, value);
      range_tags.add(right, -value);
    }

    long long point_tag(int distance) const {
      assert(0 <= distance && distance < distance_count);
      return range_tags.prefix_sum(distance + 1);
    }
  };

  struct ancestor_entry {
    int centroid = -1;
    int distance = 0;
    int branch = -1;
  };

  int vertex_count_ = 0;
  std::vector<std::vector<int>> graph_;
  std::vector<char> removed_;
  std::vector<int> component_parent_;
  std::vector<int> subtree_size_;
  std::vector<std::vector<ancestor_entry>> ancestors_;
  std::vector<bucket> all_;
  std::vector<std::vector<bucket>> branches_;
  std::vector<long long> initial_values_;

  int find_centroid(int start) {
    std::vector<int> order;
    order.push_back(start);
    component_parent_[start] = -1;
    for (int index = 0; index < int(order.size()); index++) {
      int vertex = order[index];
      for (int next : graph_[vertex]) {
        if (removed_[next] || next == component_parent_[vertex]) {
          continue;
        }
        component_parent_[next] = vertex;
        order.push_back(next);
      }
    }

    for (int index = int(order.size()) - 1; index >= 0; index--) {
      int vertex = order[index];
      subtree_size_[vertex] = 1;
      for (int next : graph_[vertex]) {
        if (!removed_[next] && component_parent_[next] == vertex) {
          subtree_size_[vertex] += subtree_size_[next];
        }
      }
    }

    int component_size = int(order.size());
    int centroid = start;
    int best_largest_part = component_size;
    for (int vertex : order) {
      int largest_part = component_size - subtree_size_[vertex];
      for (int next : graph_[vertex]) {
        if (!removed_[next] && component_parent_[next] == vertex) {
          largest_part = std::max(largest_part, subtree_size_[next]);
        }
      }
      if (largest_part < best_largest_part) {
        best_largest_part = largest_part;
        centroid = vertex;
      }
    }
    return centroid;
  }

  void decompose(int start) {
    int centroid = find_centroid(start);
    removed_[centroid] = true;
    ancestors_[centroid].push_back({centroid, 0, -1});

    int maximum_distance = 0;
    for (int neighbor : graph_[centroid]) {
      if (removed_[neighbor]) {
        continue;
      }
      int branch = int(branches_[centroid].size());
      int branch_maximum_distance = 0;
      std::vector<std::tuple<int, int, int>> stack = {
          {neighbor, centroid, 1}};
      while (!stack.empty()) {
        auto [vertex, parent, distance] = stack.back();
        stack.pop_back();
        ancestors_[vertex].push_back({centroid, distance, branch});
        branch_maximum_distance =
            std::max(branch_maximum_distance, distance);
        for (int next : graph_[vertex]) {
          if (!removed_[next] && next != parent) {
            stack.push_back({next, vertex, distance + 1});
          }
        }
      }
      branches_[centroid].push_back({});
      branches_[centroid].back().reset(branch_maximum_distance + 1);
      maximum_distance =
          std::max(maximum_distance, branch_maximum_distance);
    }
    all_[centroid].reset(maximum_distance + 1);

    for (int neighbor : graph_[centroid]) {
      if (!removed_[neighbor]) {
        decompose(neighbor);
      }
    }
  }

  long long prefix_contour_sum(int vertex, int distance_bound) const {
    long long result = 0;
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      int remaining = distance_bound - entry.distance;
      result += all_[entry.centroid].point_prefix(remaining);
      if (entry.branch != -1) {
        result -= branches_[entry.centroid][entry.branch].point_prefix(remaining);
      }
    }
    return result;
  }

public:
  explicit tree_contour(const std::vector<std::vector<int>> &graph,
                        const std::vector<long long> &initial_values = {})
      : vertex_count_(int(graph.size())), graph_(graph),
        removed_(vertex_count_), component_parent_(vertex_count_),
        subtree_size_(vertex_count_), ancestors_(vertex_count_),
        all_(vertex_count_), branches_(vertex_count_),
        initial_values_(initial_values.empty()
                            ? std::vector<long long>(vertex_count_)
                            : initial_values) {
    assert(int(initial_values_.size()) == vertex_count_);
    if (vertex_count_ == 0) {
      return;
    }
    decompose(0);
    for (int vertex = 0; vertex < vertex_count_; vertex++) {
      point_add(vertex, initial_values_[vertex]);
    }
  }

  /// @brief Add value to one vertex for later contour-sum queries.
  void point_add(int vertex, long long value) {
    assert(0 <= vertex && vertex < vertex_count_);
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      all_[entry.centroid].point_add(entry.distance, value);
      if (entry.branch != -1) {
        branches_[entry.centroid][entry.branch].point_add(entry.distance,
                                                          value);
      }
    }
  }

  /// @brief Sum values at vertices whose distance from vertex is in [l,r).
  long long range_sum(int vertex, int left, int right) const {
    assert(0 <= vertex && vertex < vertex_count_ && left <= right);
    return prefix_contour_sum(vertex, right) -
           prefix_contour_sum(vertex, left);
  }

  /// @brief Add value to vertices whose distance from vertex is in [l,r).
  void range_add(int vertex, int left, int right, long long value) {
    assert(0 <= vertex && vertex < vertex_count_ && left <= right);
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      int shifted_left = left - entry.distance;
      int shifted_right = right - entry.distance;
      all_[entry.centroid].range_add(shifted_left, shifted_right, value);
      if (entry.branch != -1) {
        branches_[entry.centroid][entry.branch].range_add(
            shifted_left, shifted_right, value);
      }
    }
  }

  /// @brief Return the initial value plus every contour-range addition that
  /// contains this vertex.
  long long point_get(int vertex) const {
    assert(0 <= vertex && vertex < vertex_count_);
    long long result = initial_values_[vertex];
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      result += all_[entry.centroid].point_tag(entry.distance);
      if (entry.branch != -1) {
        result -=
            branches_[entry.centroid][entry.branch].point_tag(entry.distance);
      }
    }
    return result;
  }
};

} // namespace noya

#endif // NOYA_TREE_CONTOUR_HPP
#include <algorithm>
#include <cassert>
#include <tuple>
#include <utility>
#include <vector>

/// @complexity Time: O(n log^2 n) construction and O(log^2 n) per update or
/// query. Space: O(n log n).

namespace noya {

/// @brief Maintain values indexed by tree distance. Every vertex is recorded
/// in all centroid-ancestor buckets; querying adds all buckets and subtracts
/// the bucket of the branch containing the query vertex. Fenwick trees over
/// distances support both point-add/range-sum and the dual range-add/point-get.
class tree_contour {
  struct fenwick {
    std::vector<long long> data;

    fenwick() = default;
    explicit fenwick(int size) : data(size + 1) {}

    int size() const { return int(data.size()) - 1; }

    void add(int index, long long value) {
      assert(0 <= index && index < size());
      for (index++; index < int(data.size()); index += index & -index) {
        data[index] += value;
      }
    }

    long long prefix_sum(int count) const {
      count = std::clamp(count, 0, size());
      long long result = 0;
      for (; count > 0; count -= count & -count) {
        result += data[count];
      }
      return result;
    }
  };

  struct bucket {
    int distance_count = 0;
    fenwick point_values;
    fenwick range_tags;

    void reset(int count) {
      distance_count = count;
      point_values = fenwick(count);
      range_tags = fenwick(count + 1);
    }

    void point_add(int distance, long long value) {
      point_values.add(distance, value);
    }

    long long point_prefix(int distance_bound) const {
      return point_values.prefix_sum(distance_bound);
    }

    void range_add(int left, int right, long long value) {
      left = std::clamp(left, 0, distance_count);
      right = std::clamp(right, 0, distance_count);
      if (left >= right) {
        return;
      }
      range_tags.add(left, value);
      range_tags.add(right, -value);
    }

    long long point_tag(int distance) const {
      assert(0 <= distance && distance < distance_count);
      return range_tags.prefix_sum(distance + 1);
    }
  };

  struct ancestor_entry {
    int centroid = -1;
    int distance = 0;
    int branch = -1;
  };

  int vertex_count_ = 0;
  std::vector<std::vector<int>> graph_;
  std::vector<char> removed_;
  std::vector<int> component_parent_;
  std::vector<int> subtree_size_;
  std::vector<std::vector<ancestor_entry>> ancestors_;
  std::vector<bucket> all_;
  std::vector<std::vector<bucket>> branches_;
  std::vector<long long> initial_values_;

  int find_centroid(int start) {
    std::vector<int> order;
    order.push_back(start);
    component_parent_[start] = -1;
    for (int index = 0; index < int(order.size()); index++) {
      int vertex = order[index];
      for (int next : graph_[vertex]) {
        if (removed_[next] || next == component_parent_[vertex]) {
          continue;
        }
        component_parent_[next] = vertex;
        order.push_back(next);
      }
    }

    for (int index = int(order.size()) - 1; index >= 0; index--) {
      int vertex = order[index];
      subtree_size_[vertex] = 1;
      for (int next : graph_[vertex]) {
        if (!removed_[next] && component_parent_[next] == vertex) {
          subtree_size_[vertex] += subtree_size_[next];
        }
      }
    }

    int component_size = int(order.size());
    int centroid = start;
    int best_largest_part = component_size;
    for (int vertex : order) {
      int largest_part = component_size - subtree_size_[vertex];
      for (int next : graph_[vertex]) {
        if (!removed_[next] && component_parent_[next] == vertex) {
          largest_part = std::max(largest_part, subtree_size_[next]);
        }
      }
      if (largest_part < best_largest_part) {
        best_largest_part = largest_part;
        centroid = vertex;
      }
    }
    return centroid;
  }

  void decompose(int start) {
    int centroid = find_centroid(start);
    removed_[centroid] = true;
    ancestors_[centroid].push_back({centroid, 0, -1});

    int maximum_distance = 0;
    for (int neighbor : graph_[centroid]) {
      if (removed_[neighbor]) {
        continue;
      }
      int branch = int(branches_[centroid].size());
      int branch_maximum_distance = 0;
      std::vector<std::tuple<int, int, int>> stack = {
          {neighbor, centroid, 1}};
      while (!stack.empty()) {
        auto [vertex, parent, distance] = stack.back();
        stack.pop_back();
        ancestors_[vertex].push_back({centroid, distance, branch});
        branch_maximum_distance =
            std::max(branch_maximum_distance, distance);
        for (int next : graph_[vertex]) {
          if (!removed_[next] && next != parent) {
            stack.push_back({next, vertex, distance + 1});
          }
        }
      }
      branches_[centroid].push_back({});
      branches_[centroid].back().reset(branch_maximum_distance + 1);
      maximum_distance =
          std::max(maximum_distance, branch_maximum_distance);
    }
    all_[centroid].reset(maximum_distance + 1);

    for (int neighbor : graph_[centroid]) {
      if (!removed_[neighbor]) {
        decompose(neighbor);
      }
    }
  }

  long long prefix_contour_sum(int vertex, int distance_bound) const {
    long long result = 0;
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      int remaining = distance_bound - entry.distance;
      result += all_[entry.centroid].point_prefix(remaining);
      if (entry.branch != -1) {
        result -= branches_[entry.centroid][entry.branch].point_prefix(remaining);
      }
    }
    return result;
  }

public:
  explicit tree_contour(const std::vector<std::vector<int>> &graph,
                        const std::vector<long long> &initial_values = {})
      : vertex_count_(int(graph.size())), graph_(graph),
        removed_(vertex_count_), component_parent_(vertex_count_),
        subtree_size_(vertex_count_), ancestors_(vertex_count_),
        all_(vertex_count_), branches_(vertex_count_),
        initial_values_(initial_values.empty()
                            ? std::vector<long long>(vertex_count_)
                            : initial_values) {
    assert(int(initial_values_.size()) == vertex_count_);
    if (vertex_count_ == 0) {
      return;
    }
    decompose(0);
    for (int vertex = 0; vertex < vertex_count_; vertex++) {
      point_add(vertex, initial_values_[vertex]);
    }
  }

  /// @brief Add value to one vertex for later contour-sum queries.
  void point_add(int vertex, long long value) {
    assert(0 <= vertex && vertex < vertex_count_);
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      all_[entry.centroid].point_add(entry.distance, value);
      if (entry.branch != -1) {
        branches_[entry.centroid][entry.branch].point_add(entry.distance,
                                                          value);
      }
    }
  }

  /// @brief Sum values at vertices whose distance from vertex is in [l,r).
  long long range_sum(int vertex, int left, int right) const {
    assert(0 <= vertex && vertex < vertex_count_ && left <= right);
    return prefix_contour_sum(vertex, right) -
           prefix_contour_sum(vertex, left);
  }

  /// @brief Add value to vertices whose distance from vertex is in [l,r).
  void range_add(int vertex, int left, int right, long long value) {
    assert(0 <= vertex && vertex < vertex_count_ && left <= right);
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      int shifted_left = left - entry.distance;
      int shifted_right = right - entry.distance;
      all_[entry.centroid].range_add(shifted_left, shifted_right, value);
      if (entry.branch != -1) {
        branches_[entry.centroid][entry.branch].range_add(
            shifted_left, shifted_right, value);
      }
    }
  }

  /// @brief Return the initial value plus every contour-range addition that
  /// contains this vertex.
  long long point_get(int vertex) const {
    assert(0 <= vertex && vertex < vertex_count_);
    long long result = initial_values_[vertex];
    for (const ancestor_entry &entry : ancestors_[vertex]) {
      result += all_[entry.centroid].point_tag(entry.distance);
      if (entry.branch != -1) {
        result -=
            branches_[entry.centroid][entry.branch].point_tag(entry.distance);
      }
    }
    return result;
  }
};

} // namespace noya