競技プログラミング用 C++ ライブラリ
components/binomial_prefix_online.hpp · GitHub で見る
共通ページ
二項係数の prefix sum の通常版・軽量版で共有する内部実装です。利用するときは公開ヘッダーを include してください。
binomial_prefix_detail::Online<Mint,Table> は、素数 mod の要素型 Mint と階乗・逆階乗・2 の累乗を生成する Table を使います。二次元格子に正規化した prefix を保存し、近い格子点から積和で補正します。max_n は非負かつ INT_MAX と法より小さく、block_size は非負にします。
| 操作 | 内容・返り値 | 計算量 |
|---|---|---|
Online(max_n=0,block_size=0) |
上限とブロック幅を指定して構築。幅 0 は平方根を基準に決定 | \(O(N+N^2/B)\) |
build(max_n,block_size=0) |
前計算を作り直す。返り値は void
|
\(O(N+N^2/B)\) |
query(n,k) |
右端を含まない prefix を Mint で返す |
\(O(B)\) |
prod(n,l,r) |
l <= r の区間和を Mint で返す |
\(O(B)\) |
max_n() |
上限を int で返す |
\(O(1)\) |
block_size() |
ブロック幅を int で返す |
\(O(1)\) |
table_size() |
格子に保存した Mint の個数を std::size_t で返す |
\(O(1)\) |
n は前計算した範囲内で指定し、k・l・r は std::int64_t です。負の添字と n より大きい添字の二項係数は 0 として扱います。上限 0 にも対応します。
上限を \(N\)、ブロック幅を \(B\) とすると、構築は \(O(N+N^2/B)\) 時間、保存量は \(O(N+N^2/B^2)\)、取得は \(O(B)\) 時間です。
#ifndef BINOMIAL_PREFIX_ONLINE_HPP
#define BINOMIAL_PREFIX_ONLINE_HPP
#include "mint_inner_product.hpp"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include <vector>
namespace binomial_prefix_detail {
template<class Mint, class Table> class Online {
Table table_;
int max_n_ = 0, block_ = 1;
std::vector<Mint> scaled_factorial_, data_;
void check([[maybe_unused]] int n) const { assert(0 <= n && n <= max_n_); }
static std::size_t offset(int row) { return std::size_t(row) * (std::size_t(row) + 1) / 2; }
template<bool Reverse> static Mint dot(const Mint* a, const Mint* b, int size) {
if constexpr (std::is_same_v<Mint, mint>) {
return mint_detail::inner_product<Reverse>(a, b, size);
} else {
Mint sum(0);
for (int i = 0; i < size; ++i) sum += a[i] * b[Reverse ? -i : i];
return sum;
}
}
Mint range(int n, int l, int r) const {
return table_.factorial[n] * dot<true>(table_.inverse_factorial.data() + l, table_.inverse_factorial.data() + n - l, r - l);
}
public:
explicit Online(int max_n = 0, int block_size = 0) { build(max_n, block_size); }
void build(int max_n, int block_size = 0) {
assert(max_n >= 0 && block_size >= 0);
table_.build(max_n);
max_n_ = max_n;
block_ = block_size ? block_size : std::max(1, int(std::sqrt(double(max_n) + 1)));
if (max_n < 2) { scaled_factorial_.clear(); data_.clear(); return; }
scaled_factorial_.resize(std::size_t(max_n) + 1);
Mint inverse_power = table_.power2[max_n].inv() * table_.half;
for (int i = max_n; i >= 0; --i) {
scaled_factorial_[i] = table_.factorial[i] * inverse_power;
inverse_power += inverse_power;
}
int rows = max_n / block_;
data_.resize(offset(rows) + std::size_t(rows) + 1);
for (int row = 0; row <= rows; ++row) {
int n = row * block_;
Mint factor = scaled_factorial_[n] + scaled_factorial_[n], sum(0);
auto base = offset(row);
for (int column = 0; column <= row / 2; ++column) {
int k = column * block_;
if (column) sum += dot<true>(table_.inverse_factorial.data() + k - block_, table_.inverse_factorial.data() + n - k + block_, block_);
Mint value = sum * factor;
data_[base + row - column] = Mint(1) - value;
data_[base + column] = value + table_.inverse_factorial[k] * table_.inverse_factorial[n - k] * factor;
}
}
}
int max_n() const { return max_n_; }
int block_size() const { return block_; }
std::size_t table_size() const { return data_.size(); }
Mint query(int n, std::int64_t k) const {
check(n);
if (k <= 0) return Mint(0);
if (k >= std::int64_t(n) + 1) return table_.power2[n];
bool reflect = k > (n + 1) / 2;
int m = reflect ? n + 1 - int(k) : int(k);
Mint value(1);
if (m != 1) {
if (m == (n + 1) / 2) value = table_.center(n);
else {
int row = std::min(int((std::int64_t(n) + block_ / 2) / block_), max_n_ / block_);
int column = std::min(int((std::int64_t(m - 1) + block_ / 2) / block_), row);
int start = row * block_, anchor = column * block_;
int center = (n + 1) / 2;
auto distance = std::int64_t(std::abs(n - start)) + std::abs(m - 1 - anchor);
if (std::min(m - 1, center - m) <= distance) {
value = m - 1 <= center - m ? Mint(1) + range(n, 1, m) : table_.center(n) - range(n, m, center);
} else {
value = data_[offset(row) + column];
if (start < n) value -= table_.inverse_factorial[anchor] * dot<false>(scaled_factorial_.data() + start, table_.inverse_factorial.data() + start - anchor, n - start);
else if (n < start) {
int first = std::max(n, anchor);
value += table_.inverse_factorial[anchor] * dot<false>(scaled_factorial_.data() + first, table_.inverse_factorial.data() + first - anchor, start - first);
}
value *= table_.power2[n];
if (anchor < m - 1) value += range(n, anchor + 1, m);
else if (m - 1 < anchor) value -= range(n, m, std::min(anchor, n) + 1);
}
}
}
return reflect ? table_.power2[n] - value : value;
}
Mint prod(int n, std::int64_t l, std::int64_t r) const { assert(l <= r); return query(n, r) - query(n, l); }
};
}
#endif
#line 1 "components/binomial_prefix_online.hpp"
#line 1 "components/mint_inner_product.hpp"
#line 1 "math/mint.hpp"
#include <cassert>
#include <istream>
#include <ostream>
#include <type_traits>
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC push_options
#pragma GCC optimize("O3")
#endif
namespace mint_detail {
template <class I>
constexpr bool mint_integral = std::is_integral_v<I>
#if defined(__SIZEOF_INT128__)
|| std::is_same_v<I, __int128> || std::is_same_v<I, unsigned __int128>
#endif
;
struct [[gnu::warn_unused]] Mint {
using mint = Mint;
unsigned v = 0;
[[nodiscard]] static constexpr unsigned mod() { return 998244353; }
[[nodiscard, gnu::always_inline]] static unsigned reduce(unsigned long long x) {
return (x + (unsigned long long)(unsigned(x) * 998244351u) * mod()) >> 32;
}
[[gnu::always_inline]] Mint() = default;
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
Mint(I x) {
long long r = x % (long long)mod();
if (r < 0) r += mod();
v = reduce((unsigned long long)r * 932051910);
}
[[nodiscard, gnu::always_inline]] static Mint raw(unsigned x) {
Mint a;
a.v = reduce((unsigned long long)x * 932051910);
return a;
}
[[nodiscard, gnu::always_inline]] static Mint word(unsigned x) {
Mint a;
a.v = x;
return a;
}
[[nodiscard, gnu::always_inline]] unsigned val() const {
unsigned r = reduce(v);
return r < mod() ? r : r - mod();
}
friend std::istream &operator>>(std::istream &is, Mint &x) {
long long value;
if (is >> value) x = Mint(value);
return is;
}
friend std::ostream &operator<<(std::ostream &os, const Mint &x) {
return os << x.val();
}
[[gnu::always_inline]] Mint &operator+=(Mint b) {
v += b.v;
if (v >= 2 * mod()) v -= 2 * mod();
return *this;
}
[[gnu::always_inline]] Mint &operator-=(Mint b) {
v -= b.v;
if (v >> 31) v += 2 * mod();
return *this;
}
[[gnu::always_inline]] Mint &operator*=(Mint b) {
v = reduce((unsigned long long)v * b.v);
return *this;
}
Mint &operator/=(Mint b) { return *this *= b.inv(); }
Mint &operator++() { return *this += Mint(1); }
Mint &operator--() { return *this -= Mint(1); }
Mint operator++(int) {
Mint a = *this;
++*this;
return a;
}
Mint operator--(int) {
Mint a = *this;
--*this;
return a;
}
[[nodiscard, gnu::always_inline]] Mint operator+() const { return *this; }
[[nodiscard, gnu::always_inline]] Mint operator-() const { return word(v ? 2 * mod() - v : 0); }
[[nodiscard, gnu::always_inline]] Mint operator+(Mint b) const { return Mint(*this) += b; }
[[nodiscard, gnu::always_inline]] Mint operator-(Mint b) const { return Mint(*this) -= b; }
[[nodiscard, gnu::always_inline]] Mint operator*(Mint b) const { return Mint(*this) *= b; }
[[nodiscard]] Mint operator/(Mint b) const { return Mint(*this) /= b; }
[[nodiscard, gnu::always_inline]] bool operator==(Mint b) const { return (v < mod() ? v : v - mod()) == (b.v < mod() ? b.v : b.v - mod()); }
[[nodiscard, gnu::always_inline]] bool operator!=(Mint b) const { return !(*this == b); }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator+(I a, Mint b) { return Mint(a) + b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator-(I a, Mint b) { return Mint(a) - b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator*(I a, Mint b) { return Mint(a) * b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend Mint operator/(I a, Mint b) { return Mint(a) / b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend bool operator==(I a, Mint b) { return Mint(a) == b; }
template <class I, std::enable_if_t<mint_integral<I>, int> = 0>
[[nodiscard]] friend bool operator!=(I a, Mint b) { return Mint(a) != b; }
[[nodiscard]] Mint pow(long long n) const {
assert(n >= 0);
if (!n) return Mint(1);
Mint r(1), a = *this;
for (; n > 1; n >>= 1, a *= a)
if (n & 1) r *= a;
return r * a;
}
[[nodiscard, gnu::noinline]] Mint inv() const {
assert(*this != Mint(0));
Mint a2 = *this * *this, a3 = a2 * *this;
Mint a7 = a3 * a3 * *this;
Mint a31 = a7;
for (int i = 0; i < 2; ++i) a31 *= a31;
a31 *= a3;
Mint r = a7;
for (int i = 0; i < 3; ++i) r *= r;
r *= a3;
r *= r;
for (int j = 0; j < 4; ++j) {
for (int i = 0; i < 5; ++i) r *= r;
r *= a31;
}
for (int i = 0; i < 3; ++i) r *= r;
return r * a7;
}
};
} // namespace mint_detail
using mint = mint_detail::Mint;
using fps_mint = mint;
#if defined(__GNUC__) && !defined(__clang__) && (defined(__x86_64__) || defined(__i386__))
#pragma GCC pop_options
#endif
#line 5 "components/mint_inner_product.hpp"
#include <cstdint>
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(__i386__))
#include <immintrin.h>
#endif
namespace mint_detail {
template<bool Reverse> inline __uint128_t inner_product_scalar(const mint* a, const mint* b, int size) {
__uint128_t sum = 0;
int i = 0;
for (; i <= size - 4; i += 4) {
std::uint64_t part = std::uint64_t(a[i].v) * b[Reverse ? -i : i].v
+ std::uint64_t(a[i + 1].v) * b[Reverse ? -i - 1 : i + 1].v
+ std::uint64_t(a[i + 2].v) * b[Reverse ? -i - 2 : i + 2].v
+ std::uint64_t(a[i + 3].v) * b[Reverse ? -i - 3 : i + 3].v;
sum += part;
}
for (; i < size; ++i) sum += std::uint64_t(a[i].v) * b[Reverse ? -i : i].v;
return sum;
}
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(__i386__))
inline bool inner_product_has_avx2() {
#if defined(__AVX2__)
return true;
#else
static const bool enabled = __builtin_cpu_supports("avx2");
return enabled;
#endif
}
inline bool inner_product_has_avx512() {
#if defined(__AVX512F__)
return true;
#else
static const bool enabled = __builtin_cpu_supports("avx512f");
return enabled;
#endif
}
template<bool Reverse> __attribute__((target("avx2")))
__uint128_t inner_product_avx2(const mint* a, const mint* b, int size) {
const auto order = _mm256_setr_epi32(7, 6, 5, 4, 3, 2, 1, 0);
const auto zero = _mm256_setzero_si256();
auto total = zero, carry = zero;
int i = 0;
for (; i <= size - 8; i += 8) {
auto x = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(a + i));
auto y = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(b + (Reverse ? -i - 7 : i)));
if constexpr (Reverse) y = _mm256_permutevar8x32_epi32(y, order);
auto even = _mm256_mul_epu32(x, y);
auto odd = _mm256_mul_epu32(_mm256_srli_epi64(x, 32), _mm256_srli_epi64(y, 32));
auto next = _mm256_add_epi64(total, _mm256_add_epi64(even, odd));
carry = _mm256_sub_epi64(carry, _mm256_cmpgt_epi64(zero, _mm256_andnot_si256(next, total)));
total = next;
}
alignas(32) std::uint64_t low[4], high[4];
_mm256_store_si256(reinterpret_cast<__m256i*>(low), total);
_mm256_store_si256(reinterpret_cast<__m256i*>(high), carry);
__uint128_t sum = 0;
if (i < size) sum = inner_product_scalar<Reverse>(a + i, b + (Reverse ? -i : i), size - i);
for (int j = 0; j < 4; ++j) sum += (__uint128_t(high[j]) << 64) + low[j];
return sum;
}
template<bool Reverse> __attribute__((target("avx512f")))
__uint128_t inner_product_avx512(const mint* a, const mint* b, int size) {
const auto order = _mm512_setr_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
const auto zero = _mm512_setzero_si512();
const auto one = _mm512_set1_epi64(1);
auto total = zero, carry = zero;
int i = 0;
for (; i <= size - 16; i += 16) {
auto x = _mm512_loadu_si512(a + i);
auto y = _mm512_loadu_si512(b + (Reverse ? -i - 15 : i));
if constexpr (Reverse) y = _mm512_permutexvar_epi32(order, y);
auto even = _mm512_mul_epu32(x, y);
auto odd = _mm512_mul_epu32(_mm512_srli_epi64(x, 32), _mm512_srli_epi64(y, 32));
auto next = _mm512_add_epi64(total, _mm512_add_epi64(even, odd));
carry = _mm512_mask_add_epi64(carry, _mm512_cmplt_epu64_mask(next, total), carry, one);
total = next;
}
alignas(64) std::uint64_t low[8], high[8];
_mm512_store_si512(low, total);
_mm512_store_si512(high, carry);
__uint128_t sum = 0;
if (i < size) sum = inner_product_scalar<Reverse>(a + i, b + (Reverse ? -i : i), size - i);
for (int j = 0; j < 8; ++j) sum += (__uint128_t(high[j]) << 64) + low[j];
return sum;
}
#endif
template<bool Reverse = false> mint inner_product(const mint* a, const mint* b, int size) {
assert(size >= 0);
__uint128_t sum;
#if (defined(__GNUC__) || defined(__clang__)) && (defined(__x86_64__) || defined(__i386__))
if (size >= 64 && inner_product_has_avx512()) sum = inner_product_avx512<Reverse>(a, b, size);
else if (size >= 64 && inner_product_has_avx2()) sum = inner_product_avx2<Reverse>(a, b, size);
else
#endif
sum = inner_product_scalar<Reverse>(a, b, size);
return mint::word(mint::reduce(std::uint64_t(sum % mint::mod())));
}
}
#line 5 "components/binomial_prefix_online.hpp"
#include <algorithm>
#line 7 "components/binomial_prefix_online.hpp"
#include <cmath>
#include <cstddef>
#line 11 "components/binomial_prefix_online.hpp"
#include <vector>
namespace binomial_prefix_detail {
template<class Mint, class Table> class Online {
Table table_;
int max_n_ = 0, block_ = 1;
std::vector<Mint> scaled_factorial_, data_;
void check([[maybe_unused]] int n) const { assert(0 <= n && n <= max_n_); }
static std::size_t offset(int row) { return std::size_t(row) * (std::size_t(row) + 1) / 2; }
template<bool Reverse> static Mint dot(const Mint* a, const Mint* b, int size) {
if constexpr (std::is_same_v<Mint, mint>) {
return mint_detail::inner_product<Reverse>(a, b, size);
} else {
Mint sum(0);
for (int i = 0; i < size; ++i) sum += a[i] * b[Reverse ? -i : i];
return sum;
}
}
Mint range(int n, int l, int r) const {
return table_.factorial[n] * dot<true>(table_.inverse_factorial.data() + l, table_.inverse_factorial.data() + n - l, r - l);
}
public:
explicit Online(int max_n = 0, int block_size = 0) { build(max_n, block_size); }
void build(int max_n, int block_size = 0) {
assert(max_n >= 0 && block_size >= 0);
table_.build(max_n);
max_n_ = max_n;
block_ = block_size ? block_size : std::max(1, int(std::sqrt(double(max_n) + 1)));
if (max_n < 2) { scaled_factorial_.clear(); data_.clear(); return; }
scaled_factorial_.resize(std::size_t(max_n) + 1);
Mint inverse_power = table_.power2[max_n].inv() * table_.half;
for (int i = max_n; i >= 0; --i) {
scaled_factorial_[i] = table_.factorial[i] * inverse_power;
inverse_power += inverse_power;
}
int rows = max_n / block_;
data_.resize(offset(rows) + std::size_t(rows) + 1);
for (int row = 0; row <= rows; ++row) {
int n = row * block_;
Mint factor = scaled_factorial_[n] + scaled_factorial_[n], sum(0);
auto base = offset(row);
for (int column = 0; column <= row / 2; ++column) {
int k = column * block_;
if (column) sum += dot<true>(table_.inverse_factorial.data() + k - block_, table_.inverse_factorial.data() + n - k + block_, block_);
Mint value = sum * factor;
data_[base + row - column] = Mint(1) - value;
data_[base + column] = value + table_.inverse_factorial[k] * table_.inverse_factorial[n - k] * factor;
}
}
}
int max_n() const { return max_n_; }
int block_size() const { return block_; }
std::size_t table_size() const { return data_.size(); }
Mint query(int n, std::int64_t k) const {
check(n);
if (k <= 0) return Mint(0);
if (k >= std::int64_t(n) + 1) return table_.power2[n];
bool reflect = k > (n + 1) / 2;
int m = reflect ? n + 1 - int(k) : int(k);
Mint value(1);
if (m != 1) {
if (m == (n + 1) / 2) value = table_.center(n);
else {
int row = std::min(int((std::int64_t(n) + block_ / 2) / block_), max_n_ / block_);
int column = std::min(int((std::int64_t(m - 1) + block_ / 2) / block_), row);
int start = row * block_, anchor = column * block_;
int center = (n + 1) / 2;
auto distance = std::int64_t(std::abs(n - start)) + std::abs(m - 1 - anchor);
if (std::min(m - 1, center - m) <= distance) {
value = m - 1 <= center - m ? Mint(1) + range(n, 1, m) : table_.center(n) - range(n, m, center);
} else {
value = data_[offset(row) + column];
if (start < n) value -= table_.inverse_factorial[anchor] * dot<false>(scaled_factorial_.data() + start, table_.inverse_factorial.data() + start - anchor, n - start);
else if (n < start) {
int first = std::max(n, anchor);
value += table_.inverse_factorial[anchor] * dot<false>(scaled_factorial_.data() + first, table_.inverse_factorial.data() + first - anchor, start - first);
}
value *= table_.power2[n];
if (anchor < m - 1) value += range(n, anchor + 1, m);
else if (m - 1 < anchor) value -= range(n, m, std::min(anchor, n) + 1);
}
}
}
return reflect ? table_.power2[n] - value : value;
}
Mint prod(int n, std::int64_t l, std::int64_t r) const { assert(l <= r); return query(n, r) - query(n, l); }
};
}