From 6a8dd196f1f09aa1ab18ff72463fe5c544fe74a8 Mon Sep 17 00:00:00 2001 From: Not_Leonian <75620009+NotLeonian@users.noreply.github.com> Date: Wed, 8 Jul 2026 23:12:47 +0900 Subject: [PATCH] =?UTF-8?q?=E3=80=8C=E4=BA=8C=E9=A0=85=E4=BF=82=E6=95=B0?= =?UTF-8?q?=E3=81=AE=E5=92=8C=EF=BC=88=E3=82=AA=E3=83=B3=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=B3=EF=BC=89=E3=80=8D=E3=81=AE=E7=A9=BA=E9=96=93=E8=A8=88?= =?UTF-8?q?=E7=AE=97=E9=87=8F=E3=82=AA=E3=83=BC=E3=83=80=E3=83=BC=E3=82=92?= =?UTF-8?q?=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../math/combinatorics/online-binomial-sum.md | 4 +- math/combinatorics/online-binomial-sum.hpp | 83 +++++++++++++------ 2 files changed, 58 insertions(+), 29 deletions(-) diff --git a/docs/math/combinatorics/online-binomial-sum.md b/docs/math/combinatorics/online-binomial-sum.md index ff7d9bf..639d389 100644 --- a/docs/math/combinatorics/online-binomial-sum.md +++ b/docs/math/combinatorics/online-binomial-sum.md @@ -8,8 +8,6 @@ documentation_of: math/combinatorics/online-binomial-sum.hpp - 整数 $n,m$ と重み $r$ に対する二項係数の prefix sum を $\displaystyle F(n,m)=\sum_{i=0}^{n-1}r^i\binom{m}{i}$ とおく。 - 半開区間の左端、右端をそれぞれ $l,u$ とする。 $\displaystyle \sum_{i=l}^{u-1}r^i\binom{m}{i}$ をオンラインで求める。 - $\binom{m}{i}=0\;(i>m)$ として扱う。 -- 前計算を行う長さを $k$ とする。 $m$ をバケットに分け、境界を $m_0$ とおいて $F(k,m_0)$ を前計算する。 -- $d=m-m_0$ とおく。 $m=m_0+d$ では $\displaystyle F(n,m)=\sum_{j=0}^{d}r^j\binom{d}{j}F(n-j,m_0)$ を使う。 - $r=0$ や $r=-1$ でも $r+1$ による除算は行わない。 ## 使い方 @@ -33,6 +31,6 @@ documentation_of: math/combinatorics/online-binomial-sum.hpp `max_m` を $M$ とおく。 -- コンストラクタ: 時間、空間 $O(M\sqrt M)$ +- コンストラクタ: 時間 $O(M\sqrt M)$ 、空間 $O(M)$ - `binom_prefix_sum(n, m)`: 時間 $O(\sqrt M)$ - `binom_sum(l, u, m)`: 時間 $O(\sqrt M)$ diff --git a/math/combinatorics/online-binomial-sum.hpp b/math/combinatorics/online-binomial-sum.hpp index 959b8dc..2bb18ad 100644 --- a/math/combinatorics/online-binomial-sum.hpp +++ b/math/combinatorics/online-binomial-sum.hpp @@ -3,10 +3,12 @@ // Σ_{i=l}^{u-1} r^i binom(m,i) をオンラインで求める。 // 0 <= l <= u と 0 <= m <= max_m を仮定する。 -// m のバケット境界の累積和と、バケット内の二項係数を前計算する。 +// m のバケット境界の累積和を n のバケット境界でサンプルし、 +// バケット内の二項係数を前計算する。 // T は四則演算を持つ型で、std::numeric_limits::is_integer が // false の場合は T(1) から T(max_m) で除算できる。 -// 計算量は前計算 O(max_m √max_m)、クエリ O(√max_m)。 +// 時間計算量は前計算 O(max_m √max_m)、クエリ O(√max_m)。 +// 空間計算量は O(max_m)。 #include #include @@ -18,8 +20,10 @@ template struct OnlineBinomialSum { T r; std::vector prefix_sum_offset; std::vector prefix_sum_table; + std::vector prefix_term_table; std::vector weighted_binomial_offset; std::vector weighted_binomial_table; + std::vector integer_inverse; explicit OnlineBinomialSum(int m, T r = T(1)) : max_m(m), r(r) { assert(max_m >= 0); @@ -50,19 +54,6 @@ template struct OnlineBinomialSum { d - 1]; } - const int bucket_count = max_m / bucket_size + 1; - prefix_sum_offset.assign(bucket_count + 1, 0); - for (int b = 0; b < bucket_count; ++b) { - const int base = b * bucket_size; - int last = base + bucket_size; - if (last > max_m + 1) { - last = max_m + 1; - } - prefix_sum_offset[b + 1] = prefix_sum_offset[b] + last + 1; - } - prefix_sum_table.assign(prefix_sum_offset[bucket_count], T()); - - std::vector integer_inverse; if constexpr (!std::numeric_limits::is_integer) { integer_inverse.assign(max_m + 1, T()); for (int i = 1; i <= max_m; ++i) { @@ -70,17 +61,28 @@ template struct OnlineBinomialSum { } } + const int bucket_count = max_m / bucket_size + 1; + prefix_sum_offset.assign(bucket_count + 1, 0); + for (int b = 0; b < bucket_count; ++b) { + prefix_sum_offset[b + 1] = prefix_sum_offset[b] + b + 1; + } + + prefix_sum_table.assign(prefix_sum_offset[bucket_count], T()); + prefix_term_table.assign(prefix_sum_offset[bucket_count], T()); + for (int b = 0; b < bucket_count; ++b) { const int base = b * bucket_size; const int offset = prefix_sum_offset[b]; - const int row_size = prefix_sum_offset[b + 1] - offset; T sum = T(); T term = T(1); - prefix_sum_table[offset] = T(); for (int i = 0; i <= base; ++i) { - sum += term; - prefix_sum_table[offset + i + 1] = sum; + if (i % bucket_size == 0) { + const int q = i / bucket_size; + prefix_sum_table[offset + q] = sum; + prefix_term_table[offset + q] = term; + } if (i < base) { + sum += term; term *= r; term *= T(base - i); if constexpr (std::numeric_limits::is_integer) { @@ -90,9 +92,6 @@ template struct OnlineBinomialSum { } } } - for (int i = base + 2; i < row_size; ++i) { - prefix_sum_table[offset + i] = sum; - } } } @@ -106,15 +105,47 @@ template struct OnlineBinomialSum { if (n > m) { n = m + 1; } + const int bucket = m / bucket_size; const int base = bucket * bucket_size; const int d = m - base; - const int prefix_offset = prefix_sum_offset[bucket]; const int weight_offset = weighted_binomial_offset[d]; + + int last_j = d; + if (last_j >= n) { + last_j = n - 1; + } + const int first_n = n - last_j; + + int sample_index = first_n / bucket_size; + if (sample_index > bucket) { + sample_index = bucket; + } + const int sample_n = sample_index * bucket_size; + const int sample_offset = prefix_sum_offset[bucket] + sample_index; + + T sum = prefix_sum_table[sample_offset]; + T term = prefix_term_table[sample_offset]; T ans = T(); - for (int j = 0; j <= d && j < n; ++j) { - ans += weighted_binomial_table[weight_offset + j] * - prefix_sum_table[prefix_offset + n - j]; + for (int current_n = sample_n; current_n <= n; ++current_n) { + if (current_n >= first_n) { + const int j = n - current_n; + ans += weighted_binomial_table[weight_offset + j] * sum; + } + if (current_n < n) { + sum += term; + if (current_n < base) { + term *= r; + term *= T(base - current_n); + if constexpr (std::numeric_limits::is_integer) { + term /= T(current_n + 1); + } else { + term *= integer_inverse[current_n + 1]; + } + } else { + term = T(); + } + } } return ans; }