LoginSignup
7
6

More than 3 years have passed since last update.

【python】nCkを導出する方法(ABC156-D)

Last updated at Posted at 2020-02-23

ABC156-D にて必要になったので備忘。

何も考えずに実装すると

nC_k = \frac{n!}{(n-k)!k!}

$nC_k$の定義は↑なので、これをそのままコードに書くと、

import math
def comb(n,k):
    math.factorial(n) // (math.factorial(n - k) * math.factorial(k))

print(comb(4,2))
# 6

こんな感じになる。
しかし$n!$は$O(n)$なので、今回のABC156-Dのような$n\leqq10^9$の場合では計算時間が長くなり間に合わない。なんとかして計算時間を短くしなければならない。

前提知識

繰り返し二乗法とフェルマーの小定理をまずは知っている必要がある。

繰り返し二乗法

べき乗を高速に求める方法。
pythonではpow(x, n, mod)とすることで $x^n$ を $mod$ で割ったあまりを計算できる。

フェルマーの小定理

$p$ を素数とし、$a$ を $p$ の倍数でない整数($a$ と $p$ は互いに素)とするときに、以下が成り立つ。

a^{p-1} \equiv 1 (mod\, p)

要は $a^{p-1}$を$p$で割ったらあまりは1だよと言っている。

nCkを式変形する

$nC_k$は以下のように変形できる。

nC_k = \frac{n!}{(n-k)!k!}=\frac{n(n-1)(n-2)\cdots(n-k+1)}{k!}

ここで$\frac{1}{k!}$に注目する。

フェルマーの小定理から、

a^{p-1} mod\, p = 1 \\ \\
a^{p-2} mod\, p = \frac{1}{a} \\

$a^{p-2} mod\, p = \frac{1}{a}$が得られる。
よって $\frac{1}{k!} = k!^{p-2} mod\, p$が成り立ち、$k!^{p-2}$は繰返し二乗法で高速に求めることができるので、$\frac{1}{k!}$を高速に求めることが可能になる。

以上から$nC_k$は以下で表すことができる。

nC_k = n(n-1)\cdots(n-k+1) \times(k!^{p-2}\,mod\,p)

計算量は$O(k)$である。($k!$が一番時間がかかる)

実装

# O(k)
def comb(n,k):
    nCk = 1
    MOD = 10**9+7

    for i in range(n-k+1, n+1):
        nCk *= i
        nCk %= MOD

    for i in range(1,k+1):
        nCk *= pow(i,MOD-2,MOD)
        nCk %= MOD
    return nCk

print(comb(4,2))
# 6

参考

https://ja.wikipedia.org/wiki/%E3%83%95%E3%82%A7%E3%83%AB%E3%83%9E%E3%83%BC%E3%81%AE%E5%B0%8F%E5%AE%9A%E7%90%86
https://note.com/tanon_cp/n/ncff944647054

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6