先识别出圆上不相交配对就是 Catalan 数,再用 Cn = Cn-1 * (4n-2) / (n+1) 的线性递推把 O(n^2) 优化到 O(n)。
OJ: luogu
题目 ID: P1375
难度:普及+/提高
标签:动态规划递推组合计数数学Catalan
日期: 2026-06-20 08:52
题意
圆上有
问这样的配对方案一共有多少种,答案对
思路
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
int n;
i64 memo[35][35];
bool vis[35][35];
// dfs(l, r) 表示区间 [l, r] 里的点按顺序排成一段时,
// 不相交配对的方案数。
i64 dfs(int l, int r) {
if (l > r) {
return 1;
}
if (vis[l][r]) {
return memo[l][r];
}
vis[l][r] = true;
i64 ans = 0;
for (int k = l + 1; k <= r; k += 2) {
ans += dfs(l + 1, k - 1) * dfs(k + 1, r);
}
memo[l][r] = ans;
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
cout << dfs(1, 2 * n) << '\n';
return 0;
}这个暴力固定区间最左边的点,枚举它和谁配对,然后递归处理左右两段。
于是自然得到标准的 Catalan 递推:
其中
但本题
关键是继续利用 Catalan 数的等价公式:
模数
状态表
| 状态 | 含义 |
|---|---|
于是递推写成:
初值:
答案:
代码
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int MAXN = 100000 + 5;
const i64 MOD = 1000000007LL;
int n;
i64 inv[MAXN];
i64 f[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
inv[1] = 1;
for (int i = 2; i <= n + 1; i++) {
inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;
}
f[0] = 1;
// Catalan 递推公式:
// C_n = C_{n-1} * (4n-2) / (n+1)
// 在模意义下,除法改成乘逆元。
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] * (4LL * i - 2) % MOD;
f[i] = f[i] * inv[i + 1] % MOD;
}
cout << f[n] << '\n';
return 0;
}复杂度
- 线性求逆元
- 线性递推答案
- 总时间复杂度
- 空间复杂度
总结
这题和圆上不相交连线的很多题一样,本质都是 Catalan 数。
区别只在于数据范围很大,所以不能停留在区间划分的
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
