====== combinatorics.py ======
===== comb =====
==== 코드 ====
# N comb
# I {"version": "1.1"}
def comb(n: int, k: int, prime_mod: int) -> int:
if prime_mod <= k:
raise ValueError("prime_mod must be greater than k.")
if n < 0 or k < 0:
raise ValueError("n and k must be a non-negative integer.")
if k > n:
return 0
if n - k < k:
k = n - k
numer = denom = 1
for i in range(k):
numer = numer * (n - i) % prime_mod
denom = denom * (i + 1) % prime_mod
return numer * pow(denom, -1, prime_mod) % prime_mod
==== 설명 ====
* [[ps:이항 계수#값을 한번만 계산할 때|이항 계수 - 값을 한번만 계산할 때]] 참고
* 놀랍게도, n<5000 정도까지는 그냥 math.comb(n, k) % mod 로 이항계수의 정확한 값을 구해놓고 마지막에 mod만 해주는 것과 비교해서 별로 빠르지 않다..
==== 이 코드를 사용하는 문제 ====
{{topic>ps:teflib:comb&nouser&nodate}}
===== CombTable =====
==== 코드 ====
# N CombTable
# I {"version": "1.1"}
class CombTable():
"""A class that repeatedly computes C(n, k) % mod in an efficient way."""
def __init__(self, max_n: int, prime_mod: int):
if prime_mod <= max_n:
raise ValueError("prime_mod must be greater than max_n.")
self._mod = prime_mod
# fact = [1!, 2!, ..., n!]
v = 1
self._fact = [1] + [v := v * i % prime_mod for i in range(1, max_n + 1)]
# fact_inv = [1, inv(n!), inv((n-1)!), ..., inv(1!)]
v = pow(v, -1, prime_mod)
self._fact_inv = ([1, v] +
[v := v * i % prime_mod for i in range(max_n, 1, -1)])
def get(self, n: int, k: int) -> int:
if n < 0 or k < 0:
raise ValueError("n and k must be a non-negative integer.")
if k > n:
return 0
return (self._fact[n] * self._fact_inv[-k] *
self._fact_inv[k - n] % self._mod)
==== 설명 ====
* [[ps:이항 계수#값을 여러번 계산해야 할 때|이항 계수 - 값을 여러번 계산해야 할 때]] 참고
* 설명된 방법 중, O(n + logP)에 테이블을 만들어 두는 방법을 사용했다.
* 호출 횟수가 어지간히 크지 않고서는, 그냥 fact배열만 미리 계산해놓고서, 매 쿼리마다 modinv를 계산하는 방법에 비해서 그다지 빠르지 않다..
==== 이 코드를 사용하는 문제 ====
---- struct table ----
schema: ps
cols: site, prob_id, %title%, prob_level
filter: teflib ~ *CombTable*
csv: 0
----
===== linear_homogeneous_recurrence =====
==== 코드 ====
# N linear_homogeneous_recurrence
# I {"version": "1.41", "func": ["matrix.matpow"]}
def linear_homogeneous_recurrence(
coefs: list[int], seeds: list[int], n: int, mod: int = 0
) -> int:
"""[DEPRECATED] Computes (a[n] % mod) from linear homogeneous recurrence.
*** Deprecated: Use combinatorics.linear_recurrence(), instead. ***
Computes a[n] from the recurrence relation in following form in
O(logn * k^3) time.
a[n] = c[0]*a[n-1] + c[1]*a[n-2] + ... + c[k]*a[n-k+1]
Args:
coef: A list of coefficients. [c[0], c[1], ..., c[k]].
seeds: A list of seed values. [a[0], a[1], ..., a[k]].
n: n.
mod: Optional modular.
Returns:
N-th value of the recurrence relation.
Raises:
ValueError: An error occurred if n is negative.
"""
if 0 <= n < len(seeds):
return seeds[n]
elif n < 0:
raise ValueError('n should be non-negative.')
init_mat = [coefs] + [[0] * len(coefs) for _ in range(len(coefs) - 1)]
for i in range(1, len(coefs)):
init_mat[i][i - 1] = 1
gen_coefs = matrix.matpow(init_mat, n - len(coefs) + 1, mod)[0]
res = sum(c * s for c, s in zip(gen_coefs, reversed(seeds)))
return res % mod if mod > 0 else res
==== 설명 ====
* **[Deprecated]** 더 간단하고 빠른 구현체인 [[#linear_recurrence]] 함수를 사용하라.
* [[ps:선형 점화식]] 참고.
===== linear_recurrence =====
==== 코드 ====
# N linear_recurrence
# I {"version": "0.1", "func": ["_naive_convolution"]}
def linear_recurrence(
coefs: Sequence[int], seeds: Sequence[int], n: int, mod_param: int = 0
):
"""Computes (a[n] % mod) from linear homogeneous recurrence relation.
This function computes a[n] from the recurrence relation in following form.
a[n] = c[0]*a[n-1] + c[1]*a[n-2] + ... + c[k]*a[n-k+1]
(a[i] and c[i] should be integers)
The implementation is based on Bostan-Mori algorithm with naive convolution
function, with O(k^2*logn) time complexity. If k is large (>60), use
linear_recurrence_large, instead.
Args:
coefs: A list of coefficients. [c[0], c[1], ..., c[k]].
seeds: A list of seed values. [a[0], a[1], ..., a[k]].
n: n.
mod_param: Optional modular.
Returns:
a[n] % mod_param.
Raises:
ValueError: An error occurred if n is negative.
"""
if 0 <= n < len(seeds):
return seeds[n]
elif n < 0:
raise ValueError('n should be non-negative.')
mod = mod_param or (1 << 64)
d = len(coefs)
if len(seeds) > d:
seeds, n = seeds[-d:], n - (len(seeds) - d)
q = [1] + [(-x) % mod for x in coefs]
p = [x % mod for x in _naive_convolution(seeds, q)[:d]]
while n:
r = q[:]
r[1::2] = [mod - x for x in q[1::2]]
p = [x % mod for x in _naive_convolution(p, r)[n & 1 :: 2]]
q = [x % mod for x in _naive_convolution(q, r)[::2]]
n >>= 1
return p[0] - mod if mod_param == 0 and p[0] > (mod // 2) else p[0]
==== 설명 ====
* 보스탄-모리 알고리즘을 이용해서, 인접 k항의 선형 동차 점화식이 주어졌을때, n번째 항을 O(k^2logn)에 계산한다.
* k가 60이상으로 커질수 있다면, [[#linear_recurrence_large]] 를 사용하는 편이 효율적이다.
* [[ps:선형 점화식]] 참고.
==== 이 코드를 사용하는 문제 ====
---- struct table ----
schema: ps
cols: site, prob_id, %title%, prob_level
filter: teflib ~ *linear_recurrence*
csv: 0
----
===== fibonacci =====
==== 코드 ====
# N fibonacci
# I {"version": "1.2"}
def fibonacci(n: int, mod: int = 0) -> int:
"""Returns n-th Fibonacci number. f(1)=1, f(2)=1, f(3)=2, f(4)=3, ..."""
c, d = 0, 1
for bit in bin(n)[2:]:
c, d = c * (d + d - c), c * c + d * d
if bit == '1':
c, d = d, c + d
if mod:
c, d = c % mod, d % mod
return c
==== 설명 ====
* [[ps:피보나치 수]] 참고
==== 이 코드를 사용하는 문제 ====
---- struct table ----
schema: ps
cols: site, prob_id, %title%, prob_level
filter: teflib ~ *fibonacci*
csv: 0
----