组合计数进阶
概念讲解
Lucas 定理
用于计算大组合数取模。
卡特兰数
Burnside/Polya 计数
用于计算在对称性下的不同方案数。
实现
Lucas 定理
1234int lucas(long long n, long long m, int p) {
if (m == 0) return 1;
return lucas(n / p, m / p, p) * C(n % p, m % p) % p;
}卡特兰数
123456789const int MOD = 1e9 + 7;
int catalan[1000005];
void init(int n) {
catalan[0] = 1;
for (int i = 1; i <= n; i++) {
catalan[i] = catalan[i-1] * (4 * i - 2) % MOD * inv(i + 1) % MOD;
}
}应用场景
1. 大组合数取模
1int result = lucas(n, m, p);2. 括号序列计数
n 对括号的合法序列数 = 卡特兰数 C_n
3. 凸多边形三角剖分
n+2 边凸多边形的三角剖分数 = 卡特兰数 C_n
考点分析
初赛考查方式
- 选择题: 组合计数的概念
- 程序阅读: 理解组合计数
- 编程题: 组合计数应用
常见题型
- 大组合数取模
- 卡特兰数
- 对称性计数
例题精讲
例题 1:大组合数取模
题目: 求 C(n, m) mod p。
解答:
12345678910111213141516171819202122232425262728293031323334353637383940#include <iostream>
using namespace std;
const int MOD = 1e9 + 7;
int quickPowerMod(int a, int n, int mod) {
int result = 1;
a %= mod;
while (n > 0) {
if (n % 2 == 1) result = result * a % mod;
a = a * a % mod;
n /= 2;
}
return result;
}
int C(int n, int m) {
if (m < 0 || m > n) return 0;
int result = 1;
for (int i = 0; i < m; i++) {
result = result * (n - i) % MOD * quickPowerMod(i + 1, MOD - 2, MOD) % MOD;
}
return result;
}
int lucas(long long n, long long m, int p) {
if (m == 0) return 1;
return lucas(n / p, m / p, p) * C(n % p, m % p) % p;
}
int main() {
long long n, m;
cin >> n >> m;
cout << lucas(n, m, MOD) << endl;
return 0;
}练习题
- 洛谷 P3807 Lucas 定理
查看答案
思路:Lucas 定理模板题。
1234567891011121314151617181920212223242526272829303132333435363738394041424344#include <iostream>
using namespace std;
int quickPowerMod(int a, int n, int mod) {
int result = 1;
a %= mod;
while (n > 0) {
if (n % 2 == 1) result = (long long)result * a % mod;
a = (long long)a * a % mod;
n /= 2;
}
return result;
}
int C(int n, int m, int p) {
if (m < 0 || m > n) return 0;
int result = 1;
for (int i = 0; i < m; i++) {
result = (long long)result * (n - i) % p * quickPowerMod(i + 1, p - 2, p) % p;
}
return result;
}
int lucas(long long n, long long m, int p) {
if (m == 0) return 1;
return (long long)lucas(n / p, m / p, p) * C(n % p, m % p, p) % p;
}
int main() {
int T;
cin >> T;
while (T--) {
long long n, m;
int p;
cin >> n >> m >> p;
cout << lucas(n + m, n, p) << endl;
}
return 0;
}- 洛谷 P1044 栈
查看答案
思路:卡特兰数。
12345678910111213141516#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
long long catalan[n + 1];
catalan[0] = 1;
for (int i = 1; i <= n; i++) {
catalan[i] = catalan[i - 1] * (4 * i - 2) / (i + 1);
}
cout << catalan[n] << endl;
return 0;
}- 洛谷 P1976 鸡蛋饼
查看答案
思路:卡特兰数。
123456789101112131415161718192021222324252627282930313233#include <iostream>
using namespace std;
const int MOD = 1000000007;
int quickPowerMod(int a, int n, int mod) {
int result = 1;
a %= mod;
while (n > 0) {
if (n % 2 == 1) result = (long long)result * a % mod;
a = (long long)a * a % mod;
n /= 2;
}
return result;
}
int main() {
int n;
cin >> n;
// C(2n, n) / (n+1)
int numerator = 1, denominator = 1;
for (int i = 0; i < n; i++) {
numerator = (long long)numerator * (2 * n - i) % MOD;
denominator = (long long)denominator * (i + 1) % MOD;
}
int result = (long long)numerator * quickPowerMod(denominator, MOD - 2, MOD) % MOD;
result = (long long)result * quickPowerMod(n + 1, MOD - 2, MOD) % MOD;
cout << result << endl;
return 0;
}易错点
⚠️ 注意:
- Lucas 定理要求 p 是质数
- 卡特兰数有多种定义
- 组合计数要注意取模
- 进阶组合数学是难点