组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)

组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)求组合数 对于不同的数据量可以用不同的方法

大家好,欢迎来到IT知识分享网。

求组合数,对于不同的数据量可以用不同的方法。实际上只用记住最高效的那个方法即可。本文将介绍四种求组合数的办法

递推求组合数

组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)

我们需要知道一个递推式。C_{a}^{b}\textrm{} = C_{a-1}^{b-1}\textrm{}+C_{a-1}^{b}\textrm{}

怎么记忆呢?

假如我们要求从a个苹果里选b个苹果,我们可以分成两种情况

1.包含a个苹果里的苹果i(ai),那么就是C_{a-1}^{b-1}\textrm{},因为已经选了ai,再选b-1个苹果即可

2.不包含ai,就是C_{a-1}^{b}\textrm{},需要在剩下的a-1个苹果里选b个苹果

用递推式预处理,时间复杂度就大大降低了

时间复杂度O(n^{2})

int ans[2010][2010]; int mod = 1e9 + 7; void init() { for(int i = 1;i<=2005;i++){ for (int j = 0; j <= i; j++) { if (i == j || j == 0) { ans[i][j] = 1; } else { ans[i][j] = (ans[i - 1][j] + ans[i - 1][j - 1]) % mod; } } } } signed main() { int n; cin >> n; init(); while (n--) { int a, b; cin >> a >> b; cout << ans[a][b] << endl; } return 0; }

逆元求组合数

组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)

可以看到数据变大了,再用第一种的O(n^{2})就过不了了

分析:时间复杂度O(n\log n)。预处理。

组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)

const int N = 1e6 + 10; long long MAX(long long a, long long b) { return a < b ? b : a; } long long MIN(long long a, long long b) { return a < b ? a : b; } int qmi(int a, int k, int p) { int res = 1; while (k) { //后面的a其实是底数与其指数的运算结果了,是不断迭代的 //第一个a其实就是a的2的0次方 if (k & 1) res = (res * a) % p; a = (a * a) % p; //注意,a是一个不断变化的过程 //下一个a就等于上一个a的平方, k >>= 1; } return res; } int mod = 1e9 + 7; int fact[N], infact[N]; void init() { //注意!0的阶乘和逆元是1! fact[0] = 1; infact[0] = 1; for (int i = 1; i <= N; i++) { fact[i] = fact[i - 1] * i % mod; infact[i] = infact[i - 1] * qmi(i, mod - 2, mod) % mod; } } signed main() { init(); int t; cin >> t; while (t--) { int a, b; cin >> a >> b; cout << fact[a] % mod * infact[b] % mod * infact[a - b] % mod << endl; } return 0; }

有个疑惑,不能直接用定义求吗?毕竟数最大才1e5,算上快速幂的时间复杂度,

也只是O(n\log n)。

错!观察一下,最多有10000个测试样例,因此会超时,肯定需要预处理的

int C(int a, int b) {//这个就是根据定义求组合数 int ans = 1; int j = a; for (int i = 1; i <= b; i++,j--) { ans = ans * j % mod; ans = ans * qmi(i, mod - 2, mod) % mod; } return ans; }

Lucas定理求组合数

定理内容

组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)

#include<assert.h> #include<cstdio> #include<set> #include<list> #include<queue> #include<math.h> #include<stdlib.h> #include<string> #include<string.h> #include <stdio.h> #include<algorithm> #include<iomanip> #include<cmath> #include<sstream> #include<stack> #include <utility> #include<map> #include <vector> #define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0) #define inf 0x3f3f3f3f // #define int long long //#include <bits/stdc++.h> typedef long long ll; #include<iostream> using namespace std; const int N = 1e5 + 10; long long MAX(long long a, long long b) { return a < b ? b : a; } long long MIN(long long a, long long b) { return a < b ? a : b; } int qmi(int a, int k, int p) { int res = 1; while (k) { //后面的a其实是底数与其指数的运算结果了,是不断迭代的 //第一个a其实就是a的2的0次方 if (k & 1) res = (res * a) % p; a = (a * a) % p; //注意,a是一个不断变化的过程 //下一个a就等于上一个a的平方, k >>= 1; } return res; } int mod = 1e5; int C(int a, int b) {//这个就是根据定义求组合数 int ans = 1; int j = a; for (int i = 1; i <= b; i++,j--) { ans = ans * j % mod; ans = ans * qmi(i, mod - 2, mod) % mod; } return ans; } int lucas(int a, int b) { if (a < mod && b < mod) { return C(a, b); } //用递归是因为第一次a/mod可能会是1e15,还不够小 return C(a % mod, b % mod) * lucas(a / mod, b / mod) % mod; } signed main() { int t; cin >> t; while (t--) { int a, b; cin >> a >> b >> mod; cout << lucas(a, b) << endl; } return 0; }

卡特兰数求组合数

结合一道例题来理解知识点

组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)

分析:有n个0和n个1,排列成一个序列,使其任意前缀序列中0的个数不少于1的个数

我们可以画一张表格,0代表向右走一格,1代表向上走一格,从(0,0)出发,最后会走到(n,n)。从(0,0)到(n,n)的走法有C_{2n}^{n}\textrm{}。(从2n步里挑n步向上走)

我们想满足“前缀序列中0的个数不少于1的个数”,即cnt_0 >= cnt_1,代表“向右走的次数>=向上走的次数”,所以走的路径只能在红线以下,如果走到红线上以及其上方就是不合法的走法。

因此 合法的走法数 = 总走法数C_{2n}^{n}\textrm{} – 不合法的走法数。

不合法的走法数怎么求?这里有一个小规律:如果一个走法不合法,那么其路径一定会走到红线上方。这时候把红线上方的路径关于红线做轴对称,会发现终点在始终在(n-1,n+1)。

因此 从(0,0)到(n-1,n+1)的走法 = 不合法的走法 = C_{2n}^{n-1}\textrm{} = C_{2n}^{n+1}\textrm{} (挑n-1步向右走或挑n+1步向上走)

组合数学(四种求组合数的方法:递推,逆元,lucas,卡特兰数)

所以 ans = C_{2n}^{n}\textrm{} – C_{2n}^{n-1}\textrm{} =  \frac{C_{2n}^{n}\textrm{}}{n+1} (经过一系列推导得来)

int qmi(int a, int k, int p) { int res = 1; while (k) { //后面的a其实是底数与其指数的运算结果了,是不断迭代的 //第一个a其实就是a的2的0次方 if (k & 1) res = (res * a) % p; a = (a * a) % p; //注意,a是一个不断变化的过程 //下一个a就等于上一个a的平方, k >>= 1; } return res; } int C(int a, int b,int p) { int ans = 1; int j = a; for (int i = 1; i <= b; i++,j--) { ans = ans * j % p; ans = ans * qmi(i, p - 2, p) % p; } return ans; } int lucas(int a, int b,int p) { if (a < p && b < p) { return C(a, b, p); } return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p; } const int mod = 1e9 + 7; signed main() { int n; cin >> n; cout << lucas(2 * n, n, mod) % mod * qmi(n + 1, mod - 2, mod) % mod; return 0; }

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/139018.html

(0)
上一篇 2025-06-09 15:15
下一篇 2025-06-09 15:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信