Official

Ex - Rating Estimator Editorial by Nyaan


まず、計算する対象を整理しましょう。ある時点でのコンテスト \(i\) のパフォーマンスの予想値を \(p_i\) とします。今、

\[\frac{1}{n} \left(\sum_{i=1}^n p_i\right) \geq B\]

を満たすような \(n\) のうち最小のものを \(s\) とします。(そのような \(n\) が存在しない場合は \(s = N\) とします。) このとき、レーティングが変動するのはコンテスト \(s\) が最後になります。 よって、元の問題は上に定義した \(s\) および \(\sum_{i=1}^s p_i\) を求める問題に言い換えられます。
ここで、上式を変形すると

\[ \begin{aligned} &\frac{1}{n} \left(\sum_{i=1}^n p_i\right) \geq B \\ &\iff \sum_{i=1}^n p_i \geq nB \\ &\iff \sum_{i=1}^n (p_i - B) \geq 0 \end{aligned} \]

になります。よって \(q_i = p_i - B\) と定義すると、\(s\)

  • 数列 \((q_1, q_2, q_3, \dots, q_N)\)\(n\) 要素の prefix sum がはじめて \(0\) 以上になるような \(n\) (そのような \(n\) が存在しない場合は\(s = N\))

と言い換えられます。
よって、この問題は次の 3 つのクエリに高速に対応できるデータ構造を利用すれば解くことができます。

  • \(q_c\) の値を \(x\) に更新する。
  • 上に定義した \(s\) を計算する。
  • \(\sum_{i=1}^n q_n\) を計算する。

これらのクエリを処理する方法はいくつかありますが、ここでは区間の総和と prefix sum の最大値を segment tree に載せる方法を説明します。
詳しく説明します。数列 \(B = (b_1, b_2, \dots, b_n)\) に対して関数 \(f(B), g(B)\) を次のように定義します。

\[ \begin{aligned} f(B) &= b_1 + b_2 + \dots + b_n \\ g(B) &= \max\lbrace b_1, b_1 + b_2, \dots, b_1 + b_2 + \dots + b_n \rbrace \end{aligned} \]

このとき、2 つの空でない数列 \(S, T\) および 2 つを結合した数列 \(S+T\) について次の関係式が成り立ちます。

\[ \begin{aligned} f(S + T) &= f(S) + f(T) \\ g(S + T) &= \max\lbrace g(S), f(S) + g(T) \rbrace \end{aligned} \]

よって列 \(B\) に対応する区間に tuple \((f(B), g(B))\) を載せた segment tree は(単位元を適切に定義すれば)正常に動作します。また、2 番目のクエリは segment tree 上の二分探索をすれば処理できることが確認できます。
よってこの問題を \(\mathrm{O}(N + Q \log N)\) で解くことができて、これは十分高速です。

  • 実装例(C++)
#include <iomanip>
#include <iostream>
#include <utility>
#include <vector>
using namespace std;
#include "atcoder/segtree.hpp"

using T = pair<long long, long long>;
T f(T a, T b) { return {a.first + b.first, max(a.second, a.first + b.second)}; }
T ti() { return {0, -1e18}; }
using Seg = atcoder::segtree<T, f, ti>;

int main() {
  cin.tie(nullptr);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(20);

  long long N, B, Q;
  cin >> N >> B >> Q;
  vector<long long> a(N);
  for (auto& x : a) cin >> x;

  vector<T> init(N);
  for (int i = 0; i < N; i++) init[i] = {a[i] - B, a[i] - B};
  Seg seg{init};

  while (Q--) {
    long long c, x;
    cin >> c >> x;
    --c;
    a[c] = x;
    seg.set(c, {x - B, x - B});
    auto i = seg.max_right(0, [](T v) { return v.second < 0; });
    if (i != N) i++;
    auto s = seg.prod(0, i).first;
    cout << 1.0 * (s + B * i) / i << "\n";
  }
}

posted:
last update: