\(C(n,k)\) は「\(n\) 個から \(k\) 個を選ぶ方法の数」(順番は区別しない)。
競技プログラミングでは、答えを mod で求めることが多く、階乗と逆元の階乗を前計算すれば、\(1\) 回 \(O(1)\) で計算できる。
書き方は \(C(n,k)\)、\(_nC_k\)、\((n \, k)\) などいろいろあるが、どれも同じ意味。
意味
例 : A, B, C, D の 4 人から 2 人選ぶ
{A,B} {A,C} {A,D} {B,C} {B,D} {C,D} → 6 通り\(C(4, 2) = 6\) {A, B} と {B, A} は同じ選び方として 1 回だけ数える。
公式
$$C(n, k) = \frac{n!}{k! \times (n – k)!}$$
なぜこの式になるのか
\(4\) 人から \(2\) 人を「\(1\)人目、\(2\)人目」と順番を付けて選ぶと、\(4 \times 3 = 12\) 通り。
ただし、{A, B} を「A → B」「B → A」の \(2\) 回数えているので、
\(2\) 人の並べ方 \(2! = 2\) で割って \(12 \div 2 = 6\)
一般には「順番付きで \(n \times (n-1) \times \dots \times (n\,-\,k+1\)通り」を「\(k\)個の並べ方 \(k!\) 通り」で割る。
$$C(n, k) = \frac{n \times (n\,-\,1) \times \dots \times (n\,-\,k+1)}{k!}=\frac{n!}{k! \times (n – k)!}$$
性質
| 性質 | 意味 |
| \(C(n, 0) = C(n, n) = 1\) | 何も選ばない / 全部選ぶのは \(1\) 通り |
| \(C(n, 1) = n\) | \(1\) 個選ぶなら \(n\) 通り |
| \(C(n, 1) = C(n, n\,-\,k)\) | 「\(k\) 個選ぶ」=「選ばない \(n\,-\,k\) 個を決める」 |
| \(k < 0\) や \(k > n\) なら \(0\) | そんな選び方はない |
| \(C(n, k) = C(n\,-\,1,k\,-\,1)+C(n\,-\,1, k) \) | ある \(1\) 個を選ぶ場合 + 選ばない場合 |
計算方法の使い分け
| 状況 | 方法 | 前計算 | 1回 |
| \(n\) が大きい \((\sim 10^6\)、 mod あり | 階乗と逆元の階乗 | \(O(n)\) | \(O(1)\) |
| \(n\) が大きい \((\sim 5000\)、 mod あり | パスカルの三角形(mint) | \(O(n^2)\) | \(O(1)\) |
| \(n\leq66\) 、 mod なし(そのままの値がほしい) | パスカルの三角形(long long) | \(O(n^2)\) | \(O(1)\) |
コード例1 : 階乗と逆元の階乗
// n_C_k を計算する(mint)
struct combination {
vector<mint> fact, ifact;
combination(int n) : fact(n + 1), ifact(n + 1) {
fact[0] = 1;
for(int i = 1; i <= n; i++) {
fact[i] = fact[i - 1] * i; // fact[i] = i!
}
ifact[n] = fact[n].inv(); // ifact[n] = 1 / n!
for(int i = n; i >= 1; i--) {
ifact[i - 1] = ifact[i] * i; // 1/(i-1)! = i × 1/i!
}
}
mint operator()(int n, int k) {
if(k < 0 || k > n) {
return 0;
} else {
return fact[n] * ifact[k] * ifact[n - k]; // n! / (k! (n-k)!)
}
}
};
combination c(N); // n ≦ N まで使える
mint x = c(N - 1, K - 1);- \(k < 0 || k > n\) だけで、\(n\) が負のとき(例:\(c(−1, −1)\))も \(0\) になる。
- 作った大きさより大きい \(n\) を渡すと範囲外アクセス。
\(C(H+W, H)\) を使うなら combination c(H + W); のように、使う \(n\) の最大値で作る。 - inv() は少し重い(O(log mod))ので \(1\) 回だけ呼び、残りは後ろから掛け算で作る。
mod の世界での割り算(逆元)
余りの世界では普通に割れないので、「掛けると 1 になる数」(逆元)を掛けて割り算の代わりにする。
- mint なら x.inv() で逆元が出る
- 中身はフェルマーの小定理:mod p が素数なら x の逆元は x^(p−2)(x.pow(p – 2) と同じ)
- 998244353 も 1000000007 も素数なので、どちらでも使える
- x が mod の倍数(mint で 0)だと逆元はない。
n が mod 以上になると n! が 0 になるので、この方法は n < mod のときだけ使える(ABC の制約ならまず問題ない)。
コード例2 : パスカルの三角形
// mint 版(n ≦ 5000 程度)
vector<vector<mint>> C(N + 1, vector<mint>(N + 1, 0));
for (int n = 0; n <= N; n++) {
C[n][0] = 1;
for (int k = 1; k <= n; k++) C[n][k] = C[n-1][k-1] + C[n-1][k];
}
// long long 版(n ≦ 66)
vector<vector<ll>> C(67, vector<ll>(67, 0));
// 同じ書き方よく出る数え方
| 数えたいもの | 答え | 考え方 |
| \(K\) 個選ぶとき, \(i\) を必ず含む | \(C(N – 1, K – 1)\) | i を確定させて、残り N – 1 個から K – 1個 |
| \(K\) 個選ぶとき, \(i\) と \(j\) を両方含む | \(C(N – 2, K – 2)\) | 2 個確定させる |
| グリッドを右と下だけで \((0,0)\)→\((H, W)\) | \(C(H+W, H)\) | \(H+W\) 歩のうち、どの H 歩を「下」にするか |
| \(N\) 種類から重複を許して \(K\) 個 | \(C(N+K-1, K)\) | \(K\) 個の玉と \(N-1\) 本の仕切りの並べ方 |
| \(0\) と \(1\) の並び(\(0\) がA個、\(1\) がB個) | \(C(A+B, A)\) | どの位置を 0 にするか |
| \(K\) 個を \(N\) グループに分ける(各 \(1\) 個以上) | \(C(K-1, N-1)\) | \(K-1\) か所のすき間から \(N-1\) か所を区切る |
重複組み合わせ(仕切り)
\(3\) 種類 (a, b, c) から重複を許して \(4\) 個選ぶ = \(4\) 個の玉◯と \(2\) 本の仕切り | の並べ方。
○○|○|○ → a 2 個、b 1 個、c 1 個
||○○○○ → c 4 個\(6\) 個の位置から仕切りの \(2\) か所を選ぶので \(C(6,2) = 15 = C(3+4-1,4)\)。
練習問題
- ABC471 E – Sum of Square of Sum … i を含む選び方 C(N−1, K−1) で寄与を数える。
- ABC034 C – 経路 … グリッドの経路の数。
- ABC145 D – Knight … 2 種類の移動の回数を求めてから二項係数。
- ABC167 E – Colorful Blocks … 仕切りの考え方を 2 回使う。
- ABC202 D – abb aba baa … 辞書順 K 番目。mod なしの long long の表。