---
# System prepended metadata

title: Tổ hợp cơ bản (tiếp theo)

---

---
title: Tổ hợp cơ bản (tiếp theo)
---

Tổ hợp cơ bản (tiếp theo)
===

-----

###### ✍️ Author: 2School Guideline 
###### 📋 Content:

[TOC]

-----

# Những tính chất của tổ hợp
## Tổ hợp
- Phần trước ta đã được biết:

$$
C_n^k = \frac{n!}{(n-k)!\cdot\ k!}
$$

- Nhưng các bạn có biết rằng tổ hợp được xây dựng trên 1 mô hình rất nổi tiếng và đặc biệt - mang tên tam giác Pascal (https://artofproblemsolving.com/wiki/index.php/Pascal%27s_triangle).
- ![](https://imgur.com/CxqaTUG.png)


- Dòng thứ n và cột thứ k chính là bằng $C_n^k$.

## Các tính chất
- Với tam giác Pascal ở trên, ta có thể đưa ra 1 vài nhận xét:
 
    - $C_n^n$ = $C_n^0$ = 1
    - $C_n^k$ = $C_n^{n - k}$ (tính chất đối xứng)
    - $C_{‎n}^k$ = $C_{n - 1}^{k - 1}$ + $C_{n - 1}^k$ (hệ thức Pascal)


# Các cách cài đặt tổ hợp
## Cách 1
### Định nghĩa
- Lợi dụng những tính chất của tổ hợp mình đã nêu ở trên, ta dễ dàng nhận ra hoàn toàn có thể sinh được các giá trị của tổ hợp cần tìm từ trước chứ không cần phải áp dụng công thức $C_n^k = \frac{n!}{(n-k)!\cdot\ k!}$
 Với cách này thì có 2 tác dụng:
    - Tránh tràn số khi tính giai thừa trong công thức (*$n!$*).
    - Tăng tốc độ của chương trình khi gọi tổ hợp nhiều lần.
    
    Và cũng có 1 điểm yếu đó là cần độ phức tạp $O(n^2)$.
    
### Cài đặt


``` cpp
#include<bits/stdc++.h>
using namespace std;

const int N = 30;
long long C[N + 1][N + 1];

int main() {
    for (int n = 0; n <= N; ++n) {
        C[n][n] = C[0][n] = 1; // là những giá trị được định sẵn
        for (int k = 1; k < n; ++k)
            C[k][n] = C[k - 1][n - 1] + C[k][n - 1];
    }
    return 0;
}
```

**Lưu ý**: Nếu cần mod 1 số thì chỉ cần thêm mod vào các phép tính cộng.

## Cách 2
### Định nghĩa
- Đây là 1 kiến thức nâng cao giúp có thể khởi tạo toàn bộ các tổ hợp (thậm chí có mod số nguyên tố) với độ phức tạp chỉ là $O(n)$ - khắc phục điểm yếu so với cách trên. Và mỗi lần gọi tổ hợp chỉ mất $O(1)$.
- Áp dụng định lý Fermat nhỏ, ta có thể dùng nghịch đảo Modulo dùng thực hiện phép chia khi có mod 1 số nguyên tố.
- Và khi khởi tạo được toàn bộ các *n!* và nghịch đảo của *n!*, ta có thể tính được $C_n^k$ trong $O(1)$.


### Cài đặt
``` cpp
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 2e5;
const ll MOD = 1e9 + 7;

ll fac[N + 1], ifac[N + 1];

ll power(ll a, ll b) {
    ll ans = 1;
    while(b > 0) {
        if(b & 1) ans = (ans * a) % MOD;
        a = (a * a) % MOD;
        b /= 2;
    }
    return ans;
}

ll C(int k, int n) {
    ll ans = fac[n] * ifac[k] % MOD;
    return ans * ifac[n - k] % MOD;
}

int main() {
    fac[0] = 1;
    for (int i = 1; i <= N; ++i)
        fac[i] = (i * fac[i - 1]) % MOD;
    ifac[N] = power(fac[N], MOD - 2);
    for (int i = N - 1; i >= 0; --i)
        ifac[i] = ((i + 1) * ifac[i + 1]) % MOD;
    return 0;
}
```
Vì kiến thức này khá nâng cao, nên các bạn có thể đọc thêm về:
- Lũy thừa nhanh: https://vnoi.info/wiki/translate/he/Number-Theory-3.md
- Nghịch đảo Modulo: https://vnoi.info/wiki/algo/math/modular-inverse
- Tổ hợp: https://vnoi.info/wiki/translate/he/Number-Theory-5.md

# Bài tập
## Bài 1: Nhóm múa
- Lớp Tin trường G có $a$ học sinh nam và $b$ học sinh nữ. Sắp tới trường sẽ có một cuộc thi văn nghệ nên giáo viên muốn chọn ra $n$ cặp nam nữ để tham gia.

- **Yêu cầu:** Có bao nhiêu cách chọn $n$ cặp múa đại diện cho lớp Tin?

- **Input:** Số nguyên $a$, $b$, $n$ ($n, m \le 15$); n \le a, b \le 50$).
- **Output:** Số cách chọn cặp múa. Vì kết quả có thể rất lớn nên lấy modulo cho $10^9+7$.

| Input  | Output |
| ------ | ------ |
| 10 6 3 | 14400  |

## Bài 2: Nhiều đường đi quá
- Bạn Phương đang chơi một trò chơi bạn mới tìm thấy trên mạng. Nhân vật của bạn đứng ở vị trí (0, 0) trong một mê cung hình chữ nhật và bạn phải điều khiển nhân vật đi đến vị trí ($n, m$) trong mê cung. Với mỗi bước đi, bạn có thể điều khiển nhân vật đi qua phải ($i, j+1$) hoặc xuống dưới ($i+1, j$).
- Bạn Phương phát hiện có nhiều cách di chuyển để đến được đích. Vậy nên, bạn quyết định chơi lại nhiều lần để khám phá nhiều đường đi khác nhau. 

- **Yêu cầu:** Bạn Phương sẽ nói số lần bạn đã chơi vòng đó, bạn hãy xác định xem Phương đã thử hết cách di chuyển chưa nhé.

- **Input:** Lần lượt là $n, m$ - kích thước của mê cung ($n, m \le 15$) và $k$ - số lần chơi của Phương (chắc chắn $k$ nhỏ hơn hoặc bằng tổng số cách di chuyển).
- **Output:** Một dòng duy nhất là "*YES*" hoặc "*NO*" tương ứng với việc Phương đã thử hết cách di chuyển hay chưa.
    
| Input  | Output |
| ------ | ------ |
| 2 2 6  | YES    |
| 1 2 1  | NO     |

## Bài 3: Xổ số kiến thiết
- Tính tổng của các số lớn nhất trong các đoạn con gồm $k$ phần tử trong mảng $a$ có $n$ phần tử.

- **Input:**
    - Số nguyên $n$ và $k$ ($n \le 100000$; $1 \le k \le 50$).
    - Mảng $a$ gồm $n$ phần tử.
- **Output:** Tổng của các số lớn nhất trong các đoạn con gồm k phần tử modulo cho $10^9+7$

| Input  | Output |
| ------ | ------ |
| 4 2    |        |
| 6 7 6 5| 39     |
