Skip to content

top_k_sum.hpp

SECTIONData Structure INCLUDEnoya/ds/top_k_sum.hpp

动态维护最大的 min(\(k\),N) 个元素之和,以稳定句柄精确删除;插入删除 O(log N),调整 \(k\) 为均摊 O(1+所选数量变化),求和 \(O(1)\)。

Complexity: Time: \(O(\log (n + 1))\) insert/erase; \(O(1 + d)\) amortized set_k, where \(d = \lvert \min (\mathrm{new}_{k} , n) - \min (\mathrm{old}_{k} , n)\rvert\). \(O(1)\) sum query. Space: \(O(n)\).

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(log(n+1)) insert/erase; O(1+d) amortized set_k,
/// where d = abs(min(new_k,n)-min(old_k,n)). O(1) sum query.
/// Space: O(n).

#include <algorithm>
#include <cassert>
#include <iterator>
#include <set>
#include <utility>

namespace noya {

/// @brief Dynamic multiset maintaining the sum of the k largest values.
/// T arithmetic, including differences and intermediate sums, must fit.
/// At most INT_MAX insertions per object's lifetime; erase needs a live handle.
template <class T> struct top_k_sum {
  using entry = std::pair<T, int>;
  int k = 0;
  int tot = 0;
  T sum{};
  std::set<entry> hi;
  std::set<entry> lo;

  top_k_sum() = default;
  explicit top_k_sum(int k_) : k(k_) { assert(k >= 0); }

  /// @brief Insert value and return a stable handle for exact erasure.
  int insert(const T &val) {
    int id = tot++;
    entry item{val, id};
    if (int(hi.size()) < k) {
      sum += item.first;
      hi.insert(std::move(item));
    } else if (!hi.empty() && *hi.begin() < item) {
      auto it = hi.begin();
      sum += item.first - it->first;
      lo.insert(lo.end(), hi.extract(it));
      hi.insert(std::move(item));
    } else {
      lo.insert(std::move(item));
    }
    return id;
  }

  /// @brief Erase the entry identified by (value, handle).
  void erase(const T &val, int id) {
    entry tar{val, id};
    if (auto it = hi.find(tar); it != hi.end()) {
      sum -= it->first;
      hi.erase(it);
    } else {
      auto ri = lo.find(tar);
      assert(ri != lo.end());
      lo.erase(ri);
    }
    rebalance();
  }

  void set_k(int k_) {
    assert(k_ >= 0);
    k = k_;
    rebalance();
  }

  const T &query() const { return sum; }
  int size() const { return int(hi.size() + lo.size()); }

private:
  void rebalance() {
    int tar = std::min(k, size());
    while (int(hi.size()) < tar) {
      auto it = std::prev(lo.end());
      sum += it->first;
      // This value precedes every selected entry, so begin() is exact.
      hi.insert(hi.begin(), lo.extract(it));
    }
    while (int(hi.size()) > tar) {
      auto it = hi.begin();
      sum -= it->first;
      lo.insert(lo.end(), hi.extract(it));
    }
  }
};

} // namespace noya

 // NOYA_TOP_K_SUM_HPP
#ifndef NOYA_TOP_K_SUM_HPP
#define NOYA_TOP_K_SUM_HPP 1

/// @complexity Time: O(log(n+1)) insert/erase; O(1+d) amortized set_k,
/// where d = abs(min(new_k,n)-min(old_k,n)). O(1) sum query.
/// Space: O(n).

#include <algorithm>
#include <cassert>
#include <iterator>
#include <set>
#include <utility>

namespace noya {

/// @brief Dynamic multiset maintaining the sum of the k largest values.
/// T arithmetic, including differences and intermediate sums, must fit.
/// At most INT_MAX insertions per object's lifetime; erase needs a live handle.
template <class T> struct top_k_sum {
  using entry = std::pair<T, int>;
  int k = 0;
  int tot = 0;
  T sum{};
  std::set<entry> hi;
  std::set<entry> lo;

  top_k_sum() = default;
  explicit top_k_sum(int k_) : k(k_) { assert(k >= 0); }

  /// @brief Insert value and return a stable handle for exact erasure.
  int insert(const T &val) {
    int id = tot++;
    entry item{val, id};
    if (int(hi.size()) < k) {
      sum += item.first;
      hi.insert(std::move(item));
    } else if (!hi.empty() && *hi.begin() < item) {
      auto it = hi.begin();
      sum += item.first - it->first;
      lo.insert(lo.end(), hi.extract(it));
      hi.insert(std::move(item));
    } else {
      lo.insert(std::move(item));
    }
    return id;
  }

  /// @brief Erase the entry identified by (value, handle).
  void erase(const T &val, int id) {
    entry tar{val, id};
    if (auto it = hi.find(tar); it != hi.end()) {
      sum -= it->first;
      hi.erase(it);
    } else {
      auto ri = lo.find(tar);
      assert(ri != lo.end());
      lo.erase(ri);
    }
    rebalance();
  }

  void set_k(int k_) {
    assert(k_ >= 0);
    k = k_;
    rebalance();
  }

  const T &query() const { return sum; }
  int size() const { return int(hi.size() + lo.size()); }

private:
  void rebalance() {
    int tar = std::min(k, size());
    while (int(hi.size()) < tar) {
      auto it = std::prev(lo.end());
      sum += it->first;
      // This value precedes every selected entry, so begin() is exact.
      hi.insert(hi.begin(), lo.extract(it));
    }
    while (int(hi.size()) > tar) {
      auto it = hi.begin();
      sum -= it->first;
      lo.insert(lo.end(), hi.extract(it));
    }
  }
};

} // namespace noya

#endif // NOYA_TOP_K_SUM_HPP
/// @complexity Time: O(log(n+1)) insert/erase; O(1+d) amortized set_k,
/// where d = abs(min(new_k,n)-min(old_k,n)). O(1) sum query.
/// Space: O(n).

#include <algorithm>
#include <cassert>
#include <iterator>
#include <set>
#include <utility>

namespace noya {

/// @brief Dynamic multiset maintaining the sum of the k largest values.
/// T arithmetic, including differences and intermediate sums, must fit.
/// At most INT_MAX insertions per object's lifetime; erase needs a live handle.
template <class T> struct top_k_sum {
  using entry = std::pair<T, int>;
  int k = 0;
  int tot = 0;
  T sum{};
  std::set<entry> hi;
  std::set<entry> lo;

  top_k_sum() = default;
  explicit top_k_sum(int k_) : k(k_) { assert(k >= 0); }

  /// @brief Insert value and return a stable handle for exact erasure.
  int insert(const T &val) {
    int id = tot++;
    entry item{val, id};
    if (int(hi.size()) < k) {
      sum += item.first;
      hi.insert(std::move(item));
    } else if (!hi.empty() && *hi.begin() < item) {
      auto it = hi.begin();
      sum += item.first - it->first;
      lo.insert(lo.end(), hi.extract(it));
      hi.insert(std::move(item));
    } else {
      lo.insert(std::move(item));
    }
    return id;
  }

  /// @brief Erase the entry identified by (value, handle).
  void erase(const T &val, int id) {
    entry tar{val, id};
    if (auto it = hi.find(tar); it != hi.end()) {
      sum -= it->first;
      hi.erase(it);
    } else {
      auto ri = lo.find(tar);
      assert(ri != lo.end());
      lo.erase(ri);
    }
    rebalance();
  }

  void set_k(int k_) {
    assert(k_ >= 0);
    k = k_;
    rebalance();
  }

  const T &query() const { return sum; }
  int size() const { return int(hi.size() + lo.size()); }

private:
  void rebalance() {
    int tar = std::min(k, size());
    while (int(hi.size()) < tar) {
      auto it = std::prev(lo.end());
      sum += it->first;
      // This value precedes every selected entry, so begin() is exact.
      hi.insert(hi.begin(), lo.extract(it));
    }
    while (int(hi.size()) > tar) {
      auto it = hi.begin();
      sum -= it->first;
      lo.insert(lo.end(), hi.extract(it));
    }
  }
};

} // namespace noya

 // NOYA_TOP_K_SUM_HPP