二項係数・階乗・順列・多項係数・重複組合せ
lib/combinatorics/combinatorics.hpp
概要
階乗・逆元・逆階乗の表を必要な大きさまで遅延構築する。 テーブルを使う計算では法が素数で、要求する添字が法未満であることを前提とする。
インクルード
#include "combinatorics/combinatorics.hpp"
クラス
| 名前 | 説明 |
|---|---|
Combinatorics |
二項係数・階乗・順列・多項係数・重複組合せ |
依存するヘッダ(3)
-
internal/internal_math.hpp
lib/internal/internal_math.hpp -
internal/internal_type_traits.hpp
lib/internal/internal_type_traits.hpp -
modint (static_modint / dynamic_modint)
lib/number_theory/modint.hpp
このヘッダを使うライブラリ(6)
-
ベル数を $B_0$ から $B_n$ まで列挙する
lib/combinatorics/bell.hpp -
複数の二項係数部分和 $\sum_{i=0}^{k}\binom{n}{i}$ をまとめて求める
lib/combinatorics/offline_binomial_sum.hpp -
分割数を $p(0)$ から $p(n)$ まで列挙する
lib/combinatorics/partition_function.hpp -
第1種スターリング数(符号付き)s(n, 0), s(n, 1), ..., s(n, n) を求める(行)
lib/combinatorics/stirling.hpp -
第2種スターリング数・ベル数
lib/combinatorics/stirling_bell.hpp -
形式的冪級数 (FPS)
lib/convolution/formal_power_series.hpp
検証プログラム(24)
-
test/aoj/dpl/bell.test.cpp
test/aoj/dpl/bell.test.cpp -
test/aoj/dpl/stirling.test.cpp
test/aoj/dpl/stirling.test.cpp -
test/yosupo/enumerative_conbinatorics/bell_number.test.cpp
test/yosupo/enumerative_conbinatorics/bell_number.test.cpp -
test/yosupo/enumerative_conbinatorics/partition_function.test.cpp
test/yosupo/enumerative_conbinatorics/partition_function.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind_fixed_k.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_first_kind_fixed_k.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind.test.cpp -
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind_fixed_k.test.cpp
test/yosupo/enumerative_conbinatorics/stirling_number_of_the_second_kind_fixed_k.test.cpp -
test/yosupo/polynomial/composition_of_formal_power_series.test.cpp
test/yosupo/polynomial/composition_of_formal_power_series.test.cpp -
test/yosupo/polynomial/composition_of_formal_power_series_large.test.cpp
test/yosupo/polynomial/composition_of_formal_power_series_large.test.cpp -
test/yosupo/polynomial/compositional_inverse_of_formal_power_series.test.cpp
test/yosupo/polynomial/compositional_inverse_of_formal_power_series.test.cpp -
test/yosupo/polynomial/compositional_inverse_of_formal_power_series_large.test.cpp
test/yosupo/polynomial/compositional_inverse_of_formal_power_series_large.test.cpp -
test/yosupo/polynomial/division_of_polynomials.test.cpp
test/yosupo/polynomial/division_of_polynomials.test.cpp -
test/yosupo/polynomial/exp_of_formal_power_series.test.cpp
test/yosupo/polynomial/exp_of_formal_power_series.test.cpp -
test/yosupo/polynomial/inv_of_formal_power_series.test.cpp
test/yosupo/polynomial/inv_of_formal_power_series.test.cpp -
test/yosupo/polynomial/inv_of_polynomials.test.cpp
test/yosupo/polynomial/inv_of_polynomials.test.cpp -
test/yosupo/polynomial/log_of_formal_power_series.test.cpp
test/yosupo/polynomial/log_of_formal_power_series.test.cpp -
test/yosupo/polynomial/multipoint_evaluation.test.cpp
test/yosupo/polynomial/multipoint_evaluation.test.cpp -
test/yosupo/polynomial/polynomial_interpolation.test.cpp
test/yosupo/polynomial/polynomial_interpolation.test.cpp -
test/yosupo/polynomial/polynomial_taylor_shift.test.cpp
test/yosupo/polynomial/polynomial_taylor_shift.test.cpp -
test/yosupo/polynomial/pow_of_formal_power_series.test.cpp
test/yosupo/polynomial/pow_of_formal_power_series.test.cpp -
test/yosupo/polynomial/shift_of_sampling_points_of_polynomial.test.cpp
test/yosupo/polynomial/shift_of_sampling_points_of_polynomial.test.cpp -
test/yosupo/polynomial/sqrt_of_formal_power_series.test.cpp
test/yosupo/polynomial/sqrt_of_formal_power_series.test.cpp -
test/yukicoder/2206.test.cpp
test/yukicoder/2206.test.cpp
実装
#pragma once
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <vector>
#include "number_theory/modint.hpp"
/// @brief 二項係数・階乗・順列・多項係数・重複組合せ
/// @details 階乗・逆元・逆階乗の表を必要な大きさまで遅延構築する。
/// テーブルを使う計算では法が素数で、要求する添字が法未満であることを前提とする。
/// @tparam mint 法が素数の modint 型
template <internal::modint mint = modint998>
struct Combinatorics {
/// @brief 空の計算テーブルを構築する
/// @complexity $O(1)$
Combinatorics() : fact_table(), inv_table(), finv_table() {}
/// @brief $x!$ を返す
/// @param x 0以上の整数
/// @complexity 構築済みの最大添字を $m$ として、$x \le m$ なら $O(1)$、
/// それ以外は $O(x-m)$
mint fact(int x) {
assert(x >= 0);
init(x);
return fact_table[x];
}
/// @brief $(x!)^{-1}$ を返す
/// @param x 0以上かつ法未満の整数
/// @complexity 構築済みの最大添字を $m$ として、$x \le m$ なら $O(1)$、
/// それ以外は $O(x-m)$
mint finv(int x) {
assert(x >= 0);
init(x);
return finv_table[x];
}
// n が table_limit を超える場合はテーブルを持たず binom_direct() に委譲する
/// @brief 二項係数 $\binom{n}{k}$ を返す。不正な範囲では0を返す
/// @param n 要素数
/// @param k 選ぶ要素数
/// @complexity $n \le 20{,}000{,}000$ では、構築済みの最大添字を $m$ として
/// $O(1+\max(0,n-m))$
/// @complexity $n > 20{,}000{,}000$ では $O(\min(k,n-k))$
mint binom(std::int64_t n, std::int64_t k) {
if (n < k || n < 0 || k < 0) return 0;
if (n <= table_limit) {
init((int)n);
return fact_table[n] * finv_table[k] * finv_table[n - k];
}
return binom_direct(n, std::min(k, n - k));
}
// n が table_limit を超える場合はテーブルを持たず perm_direct() に委譲する
/// @brief 順列数 $\frac{n!}{(n-k)!}$ を返す。不正な範囲では0を返す
/// @param n 要素数
/// @param k 並べる要素数
/// @complexity $n \le 20{,}000{,}000$ では、構築済みの最大添字を $m$ として
/// $O(1+\max(0,n-m))$
/// @complexity $n > 20{,}000{,}000$ では $O(k)$
mint perm(std::int64_t n, std::int64_t k) {
if (n < k || n < 0 || k < 0) return 0;
if (n <= table_limit) {
init((int)n);
return fact_table[n] * finv_table[n - k];
}
return perm_direct(n, k);
}
// 多項係数 n! / (k[0]! k[1]! ... k[r-1]!)(sum(k) == n)
/// @brief 多項係数 $\frac{n!}{\prod_i k_i!}$ を返す
/// @param n 要素数
/// @param ks 各グループの要素数。総和が n であること
/// @complexity 構築済みの最大添字を $m$、ks の要素数を $r$ として
/// $O(1+\max(0,n-m)+r)$
mint multinomial(int n, const std::vector<int> &ks) {
assert(n >= 0);
int sum = 0;
for (int k : ks) {
assert(k >= 0);
sum += k;
}
assert(sum == n);
init(n);
mint res = fact_table[n];
for (int k : ks) res *= finv_table[k];
return res;
}
// 重複組合せ(n 種類から重複を許して k 個選ぶ場合の数)nHk = C(n+k-1, k)
/// @brief n種類から重複を許してk個選ぶ組合せ数 $\binom{n+k-1}{k}$ を返す
/// @param n 種類数
/// @param k 選ぶ個数
/// @complexity `binom(n + k - 1, k)` と同じ
mint multiset(std::int64_t n, std::int64_t k) {
if (n < 0 || k < 0) return 0;
if (n == 0) return k == 0 ? 1 : 0;
return binom(n + k - 1, k);
}
// Lucas の定理: mod が素数のとき n, k が mod 程度まで巨大でも C(n,k) mod を計算する
// (base-mod の各桁 n_i, k_i < mod での binom() に帰着。mod 自体が大きいと binom_direct
// の O(min(k_i, n_i-k_i)) が支配的になり実用的でなくなる点に注意)
/// @brief Lucasの定理で二項係数 $\binom{n}{k}$ を返す
/// @details nとkを法の基数で各桁に分解し、桁ごとの二項係数を掛け合わせる。
/// @param n 要素数
/// @param k 選ぶ要素数
/// @complexity $O(\log_{\mathrm{mod}} n)$ 回の `binom` 呼び出し
mint binom_lucas(std::int64_t n, std::int64_t k) {
assert(mint::is_prime_mod());
if (n < k || n < 0 || k < 0) return 0;
mint res = 1;
while (k > 0) {
std::int64_t ni = n % mod, ki = k % mod;
if (ki > ni) return 0;
res *= binom(ni, ki);
n /= mod;
k /= mod;
}
return res;
}
private:
// テーブルを確保する n の上限(超えると direct()/perm_direct() にフォールバック)
static constexpr std::int64_t table_limit = 20'000'000;
static constexpr int mod = mint::mod();
std::vector<mint> fact_table, inv_table, finv_table;
mint binom_direct(std::int64_t n, std::int64_t k) const {
if (n < k || n < 0 || k < 0) return 0;
if (n - k < k) k = n - k;
mint res = 1;
for (std::int64_t i = 0; i < k; ++i) {
res *= n - i;
res /= i + 1;
}
return res;
}
mint perm_direct(std::int64_t n, std::int64_t k) const {
if (n < k || n < 0 || k < 0) return 0;
mint res = 1;
for (std::int64_t i = 0; i < k; ++i) res *= n - i;
return res;
}
void init(int n) {
if ((int)fact_table.size() > n) return;
int m = fact_table.size();
fact_table.resize(n + 1);
for (int i = m; i <= n; ++i) {
if (i == 0) fact_table[i] = 1;
else fact_table[i] = fact_table[i - 1] * i;
}
inv_table.resize(n + 1);
for (int i = m; i <= n; ++i) {
if (i <= 1) inv_table[i] = 1;
else inv_table[i] = -inv_table[mod % i] * (mod / i);
}
finv_table.resize(n + 1);
for (int i = m; i <= n; ++i) {
if (i == 0) finv_table[i] = 1;
else finv_table[i] = finv_table[i - 1] * inv_table[i];
}
}
};