---
tags: AtCoder, DP, Combinatorics, SPyofgame, kazamahoang
---
AtCoder Beginner Contest 217 F - Make Pair
===
[https://atcoder.jp/contests/abc217/tasks/abc217_f](https://atcoder.jp/contests/abc217/tasks/abc217_f)
---
###### Tags: `DP`, `Combinatorics`.
---
### Hướng dẫn
Nhận thấy, trong một đoạn $[L, R]$ ($L, R$ khác tính chẵn lẻ), việc ghép đôi người ở vị trí $L$ với người ở vị trí $K$ $(L < K \leq R)$ chia đoạn $[L, R]$ ra thành hai đoạn con $[L+1, K-1]$ và $[K+1, R]$. Bên cạnh đó, số cách chọn cặp ghép đôi ở mỗi đoạn con độc lập với nhau.
Ta định nghĩa như sau:
- Kết quả của đoạn $[L+1, K-1]$ là $lf$.
- Kết quả của đoạn $[K+1, R]$ là $rt$.
- Số phép ghép đôi trong đoạn $[L+1, K-1]$ là $X = \frac{K-L-1}{2}$,
- Số phép ghép đôi trong đoạn $[K+1, R]$ là $Y = \frac{R-K}{2}$.
Ta có $lf \times rt$ cách chọn $X+Y$ phép ghép đôi. Trong mỗi cách chọn, lại có ${X+Y+1 \choose Y}$ cách lựa chọn thứ tự của các phép ghép đôi (bao gồm cả phép ghép đôi giữa $L$ và $K$).
Cuối cùng, ta xây dựng được công thức quy hoạch động như sau:
- Gọi $dp[L][R]$ là kết quả nếu xét những người $a_L, a_{L+1}, ..., a_R$
- Gọi $C[N][K] = \binom{N}{K}$ là tổ hợp chập $K$ của $N$
- Công thức: $dp[L][R] = \overset{R}{\underset{k = L+1}{\LARGE \Sigma}} \left(dp[L+1][K-1] \times dp[K+1][R] \times C\left[\frac{K-L-1}{2}+\frac{R-K}{2}+1\right]\left[\frac{R-K}{2} \right] \right)$.
- Cơ sở quy hoạch động:
- Khởi tạo mảng $dp[i][j] = 0$.
- $dp[i][i+1]= 1$.
- Đáp số: $dp[1][2n]$.
---
### Độ phức tạp và cài đặt
Đặt $N = 2 \times n$
Số trạng thái quy hoạch động là $N^2$.
Mỗi trạng thái có chi phí chuyển là $O(N)$.
Ngoài ra việc chuẩn bị mảng tổ hợp mất chi phí là $O(N^2)$.
Vậy tổng độ phức tạp là $O(N^3)$.
```cpp=
#include <bits/stdc++.h>
#define F first
#define S second
using namespace std;
using ll = long long;
const int inf = 1061109567;
const ll INF = 4557430888798830399;
const int MOD = 998244353;
int comb[205][205]; // mảng tổ hợp
void init(int n) {
for (int i = 0; i <= n; ++ i) {
comb[0][i] = comb[i][i] = 1;
for (int j = 1; j < i; ++ j)
comb[j][i] = (comb[j][i-1] + comb[j-1][i-1]) % MOD;
}
}
int n, m, dp[405][405];
bool good[405][405];
int solve(int l, int r) { // solve for [l, r]
assert(l <= r + 1);
if (l > r) return 1;
int& ret = dp[l][r];
if (ret != -1) return ret;
ret = 0; // khởi tạo dp = 0 để tính toán
for (int k = l + 1; k <= r; ++ k) {
if (!good[l][k] || (k - l) % 2 == 0) continue;
// (l, k) được ghép đôi
int lf = solve(l + 1, k - 1); // solve for [l+1, i-1]
int rt = solve(k + 1, r); // solve for [i+1, r]
int cnt_lf = (k - l - 1) / 2 + 1; // (l, i) inclusive
int cnt_rt = (r - k) / 2;
ret += (ll)lf * rt % MOD * comb[cnt_lf][cnt_lf+cnt_rt] % MOD;
ret %= MOD;
}
return ret;
}
int32_t main(void) {
cin.tie(0)->sync_with_stdio(0);
cin >> n >> m;
for (int i = 1; i <= m; ++ i) {
int u, v;
cin >> u >> v;
good[u][v] = good[v][u] = true; // good term
}
init(n); // khởi tạo mảng tổ hợp
memset(dp, -1, sizeof dp); // khởi tạo -1 cho việc dp top down
cout << solve(1, 2 * n) << "\n";
return 0;
}
// Written by Kazama Hoang ^^
```