Skip to content

range_parallel_dsu.hpp

SECTIONData Structure INCLUDEnoya/range_parallel_dsu.hpp

Add all equal-offset edges between two ranges and maintain a component-pair aggregate. Level h represents intervals of length 2^h. Merging two level-h classes recursively merges their left and right halves; since each class pair succeeds only once, all downward propagation is amortized over O(n log n) interval nodes. Any length is covered by its first and last largest-power-of-two blocks.

Verified by range_parallel_unionfind.

批量合并两个等长区间中对应位置的点;适合大量区间等价约束或字符串片段对应关系。

Implementation

View on GitHub

#ifndef NOYA_RANGE_PARALLEL_DSU_HPP
#define NOYA_RANGE_PARALLEL_DSU_HPP 1

/// @complexity Time: O((n + q) log n alpha(n)) total for q range unions.
/// Space: O(n log n).

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

namespace noya {

/// @brief Add all equal-offset edges between two ranges and maintain a
/// component-pair aggregate. Level h represents intervals of length 2^h.
/// Merging two level-h classes recursively merges their left and right halves;
/// since each class pair succeeds only once, all downward propagation is
/// amortized over O(n log n) interval nodes. Any length is covered by its first
/// and last largest-power-of-two blocks.
template <class T> class range_parallel_dsu {
public:
  range_parallel_dsu() = default;

  explicit range_parallel_dsu(const std::vector<T> &weights) {
    build(weights);
  }

  void build(const std::vector<T> &weights) {
    size_ = int(weights.size());
    int levels = 1;
    while ((1 << levels) <= std::max(1, size_)) {
      levels++;
    }
    parent_.resize(levels);
    for (int level = 0; level < levels; level++) {
      int length = 1 << level;
      int count = std::max(0, size_ - length + 1);
      parent_[level].assign(count, -1);
    }
    component_sum_ = weights;
    pair_sum_ = T{};
  }

  /// @brief Add edges (first+i,second+i) for 0<=i<length and return the new
  /// sum of weight products over unordered pairs in the same component.
  T unite_ranges(int first, int second, int length) {
    assert(length >= 0);
    assert(0 <= first && first + length <= size_);
    assert(0 <= second && second + length <= size_);
    if (length == 0) {
      return pair_sum_;
    }
    int level = 31 - __builtin_clz(unsigned(length));
    int block = 1 << level;
    unite_blocks(level, first, second);
    unite_blocks(level, first + length - block, second + length - block);
    return pair_sum_;
  }

  T pair_sum() const { return pair_sum_; }

  bool same(int first, int second) {
    assert(0 <= first && first < size_ && 0 <= second && second < size_);
    return find(0, first) == find(0, second);
  }

private:
  int size_ = 0;
  std::vector<std::vector<int>> parent_;
  std::vector<T> component_sum_;
  T pair_sum_{};

  int find(int level, int value) {
    int root = value;
    while (parent_[level][root] >= 0) {
      root = parent_[level][root];
    }
    while (value != root) {
      int next = parent_[level][value];
      parent_[level][value] = root;
      value = next;
    }
    return root;
  }

  bool join_roots(int level, int first, int second) {
    first = find(level, first);
    second = find(level, second);
    if (first == second) {
      return false;
    }
    if (parent_[level][first] > parent_[level][second]) {
      std::swap(first, second);
    }
    parent_[level][first] += parent_[level][second];
    parent_[level][second] = first;
    if (level == 0) {
      pair_sum_ += component_sum_[first] * component_sum_[second];
      component_sum_[first] += component_sum_[second];
    }
    return true;
  }

  void unite_blocks(int level, int first, int second) {
    if (!join_roots(level, first, second) || level == 0) {
      return;
    }
    int half = 1 << (level - 1);
    unite_blocks(level - 1, first, second);
    unite_blocks(level - 1, first + half, second + half);
  }
};

} // namespace noya

#endif // NOYA_RANGE_PARALLEL_DSU_HPP
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <utility>
#include <vector>

/// @complexity Time: O((n + q) log n alpha(n)) total for q range unions.
/// Space: O(n log n).

namespace noya {

/// @brief Add all equal-offset edges between two ranges and maintain a
/// component-pair aggregate. Level h represents intervals of length 2^h.
/// Merging two level-h classes recursively merges their left and right halves;
/// since each class pair succeeds only once, all downward propagation is
/// amortized over O(n log n) interval nodes. Any length is covered by its first
/// and last largest-power-of-two blocks.
template <class T> class range_parallel_dsu {
public:
  range_parallel_dsu() = default;

  explicit range_parallel_dsu(const std::vector<T> &weights) {
    build(weights);
  }

  void build(const std::vector<T> &weights) {
    size_ = int(weights.size());
    int levels = 1;
    while ((1 << levels) <= std::max(1, size_)) {
      levels++;
    }
    parent_.resize(levels);
    for (int level = 0; level < levels; level++) {
      int length = 1 << level;
      int count = std::max(0, size_ - length + 1);
      parent_[level].assign(count, -1);
    }
    component_sum_ = weights;
    pair_sum_ = T{};
  }

  /// @brief Add edges (first+i,second+i) for 0<=i<length and return the new
  /// sum of weight products over unordered pairs in the same component.
  T unite_ranges(int first, int second, int length) {
    assert(length >= 0);
    assert(0 <= first && first + length <= size_);
    assert(0 <= second && second + length <= size_);
    if (length == 0) {
      return pair_sum_;
    }
    int level = 31 - __builtin_clz(unsigned(length));
    int block = 1 << level;
    unite_blocks(level, first, second);
    unite_blocks(level, first + length - block, second + length - block);
    return pair_sum_;
  }

  T pair_sum() const { return pair_sum_; }

  bool same(int first, int second) {
    assert(0 <= first && first < size_ && 0 <= second && second < size_);
    return find(0, first) == find(0, second);
  }

private:
  int size_ = 0;
  std::vector<std::vector<int>> parent_;
  std::vector<T> component_sum_;
  T pair_sum_{};

  int find(int level, int value) {
    int root = value;
    while (parent_[level][root] >= 0) {
      root = parent_[level][root];
    }
    while (value != root) {
      int next = parent_[level][value];
      parent_[level][value] = root;
      value = next;
    }
    return root;
  }

  bool join_roots(int level, int first, int second) {
    first = find(level, first);
    second = find(level, second);
    if (first == second) {
      return false;
    }
    if (parent_[level][first] > parent_[level][second]) {
      std::swap(first, second);
    }
    parent_[level][first] += parent_[level][second];
    parent_[level][second] = first;
    if (level == 0) {
      pair_sum_ += component_sum_[first] * component_sum_[second];
      component_sum_[first] += component_sum_[second];
    }
    return true;
  }

  void unite_blocks(int level, int first, int second) {
    if (!join_roots(level, first, second) || level == 0) {
      return;
    }
    int half = 1 << (level - 1);
    unite_blocks(level - 1, first, second);
    unite_blocks(level - 1, first + half, second + half);
  }
};

} // namespace noya