当前位置: 首页 > news >正文

组合数常见的四种计算方法

杨辉三角法

这是最直观、最易于理解的方法之一。它基于组合数的递推关系:
C(n, k) = C(n-1, k-1) + C(n-1, k)

这个公式正是杨辉三角的生成规则。

算法步骤:

  1. 创建一个二维数组 dp,大小为 (n+1) x (n+1)

  2. 初始化边界条件:对于所有的 iC(i, 0) = 1 且 C(i, i) = 1

  3. 使用双重循环来填充这个表格:

    • 外层循环 i 从 0 到 n,代表总元素数。

    • 内层循环 j 从 1 到 i-1(因为 j=0 和 j=i 的情况已经初始化),应用递推公式 dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

  4. 最终,dp[n][k] 就是我们要的结果。

#include <stdio.h>#define MAX_K 1000  // 根据需求调整最大k值// 杨辉三角法计算组合数(固定数组)
long long comb_dp(int n, int k) {if (k < 0 || k > n) return 0;if (k == 0 || k == n) return 1;// 利用对称性优化if (k > n - k) k = n - k;long long dp[MAX_K + 1] = {0};dp[0] = 1;for (int i = 1; i <= n; i++) {// 反向遍历避免覆盖for (int j = (i < k ? i : k); j > 0; j--) {dp[j] = dp[j] + dp[j - 1];}}return dp[k];
}

乘法公式法

直接使用组合数的定义公式,但在计算时进行优化以避免中间值过大和精度丢失。

公式: C(n, k) = n! / (k! * (n-k)!)

直接计算阶乘再相除,很容易导致数值溢出。一个更好的方法是边乘边除。

算法步骤:

  1. 确保 k <= n-k,如果不满足,令 k = n - k。因为 C(n, k) = C(n, n-k),这样可以减少计算量。

  2. 计算结果 = 1。

  3. 通过一个循环从 1 到 k,每次迭代执行:结果 = 结果 * (n - i + 1) / i

这个方法的正确性在于:
C(n, k) = [n * (n-1) * ... * (n-k+1)] / [1 * 2 * ... * k]
在每一步乘法后立即进行除法,可以保证中间结果尽可能小,并且最终结果是一个整数。

#include <stdio.h>// 乘法公式计算组合数
long long comb_mult(int n, int k) {if (k < 0 || k > n) return 0;if (k == 0 || k == n) return 1;// 利用对称性优化if (k > n - k) k = n - k;long long result = 1;for (int i = 1; i <= k; i++) {result = result * (n - k + i) / i;}return result;
}

模逆元法

在编程竞赛和密码学中,我们经常需要计算 C(n, k) mod p,其中 p 可能是一个大质数(如 10^9+7)。

情况一:p 是质数(且足够大,通常大于 n)

这是最理想的情况,我们可以利用费马小定理模逆元

原理:
费马小定理:如果 p 是质数,且 a 不是 p 的倍数,则 a^(p-1) ≡ 1 (mod p)。
由此可得,a 在模 p 下的逆元 a^(-1) ≡ a^(p-2) (mod p)。

那么,组合数公式变为:
C(n, k) mod p = [ n! * inv(k!) * inv((n-k)! ) ] mod p
其中 inv(x) 表示 x 在模 p 下的逆元。

算法步骤:

  1. 预处理出 1 到 n 的阶乘 fact[i] 和对应的阶乘的逆元 inv_fact[i]

  2. 计算 C(n, k) = fact[n] * inv_fact[k] % p * inv_fact[n-k] % p

#include <stdio.h>
#include <stdlib.h>#define MOD 1000000007
#define MAX_N 1000000long long fact[MAX_N + 1];
long long inv_fact[MAX_N + 1];// 快速幂
long long power(long long a, long long b) {long long res = 1;while (b > 0) {if (b & 1) res = (res * a) % MOD;a = (a * a) % MOD;b >>= 1;}return res;
}// 预处理阶乘和逆元
void precompute() {fact[0] = 1;for (int i = 1; i <= MAX_N; i++) {fact[i] = (fact[i - 1] * i) % MOD;}inv_fact[MAX_N] = power(fact[MAX_N], MOD - 2);for (int i = MAX_N - 1; i >= 0; i--) {inv_fact[i] = (inv_fact[i + 1] * (i + 1)) % MOD;}
}// 模逆元法计算组合数
long long comb_mod(int n, int k) {if (k < 0 || k > n) return 0;return (fact[n] * inv_fact[k] % MOD) * inv_fact[n - k] % MOD;
}

卢卡斯定理

情况二:p 可能不是质数,或者 n, k 极大(如 10^18)

这时需要使用 Lucas 定理

卢卡斯定理: C(n,k) mod p = C(n mod p, k mod p) × C(n/p, k/p) mod p

将n和k表示为p进制数,组合数等于各位组合数的乘积。

这通常用于 p 不太大的情况。

算法步骤
  1. 如果k=0,返回1

  2. 递归计算:Lucas(n,k,p) = C(n%p, k%p) × Lucas(n/p, k/p, p) mod p

  3. 其中C(n%p, k%p)用简单方法计算(因为n%p, k%p < p)

#include <stdio.h>#define MOD 1000000007// 计算小组合数(n, k < MOD)
long long comb_small(int n, int k, int p) {if (k < 0 || k > n) return 0;if (k == 0 || k == n) return 1;if (k > n - k) k = n - k;long long res = 1;for (int i = 1; i <= k; i++) {res = res * (n - k + i) % p;res = res * power(i, p - 2, p) % p;}return res;
}// 带模数的快速幂
long long power(long long a, long long b, int p) {long long res = 1;while (b > 0) {if (b & 1) res = (res * a) % p;a = (a * a) % p;b >>= 1;}return res;
}// 卢卡斯定理计算组合数
long long lucas(int n, int k, int p) {if (k == 0) return 1;return lucas(n / p, k / p, p) * comb_small(n % p, k % p, p) % p;
}

总结

方法时间复杂度空间复杂度适用场景
杨辉三角(DP)O(n*k)O(k) (优化后)需要多次计算不同的小规模组合数,适合预处理。
乘法公式O(k)O(1)计算单个组合数的最快方法,n 和 k 不大,且结果在整数范围内。最常用
模逆元(p为质数)O(n) 预处理,O(1) 查询O(n)n, k 很大,需要对结果取模,且模数 p 是质数。编程竞赛标配。
卢卡斯定理O(p * log_p n)O(1)n, k 极大,但模数 p 相对较小。
http://www.dtcms.com/a/512453.html

相关文章:

  • 美容医疗服务小程序:功能集成,解锁一站式变美新体验
  • 网站建设的展望 视频搭建公司内部网站
  • Mac 从零开始配置 VS Code + Claude/Codex AI 协同开发环境教程
  • 鸿蒙flutter 老项目到新项目的遇到迁移坑点
  • 网站开发z亿玛酷1专注响应式官网设计
  • SD comfy:教程3 - 音频生成
  • 百度网盘登录福建键seo排名
  • Xshell效率实战系列一:内置Xftp快速启动——从1分钟到10秒的传输革命
  • 公路建设网站自己编写网站
  • EN 13986:2004+A1:2015 人造板检测
  • 酒店网站html模板长沙微信网站建设
  • 天河建设网站技术网站备案号密码
  • 步进电机(200 smart DM542)调试文档
  • ubuntu22 docker安装ros1 noetic
  • 惠州网站制作定制和平天津网站建设
  • 电子网站建设价格怎样建立自己网站多少钱
  • 鸿蒙开发的三种能力集以及错误的产生条件
  • 西陆房产系统小程序
  • 方正悠黑使用网站建设侵权么触屏手机网站
  • ELK运维之路(Logstash-高级功能-7.17.24)
  • Harmony鸿蒙开发0基础入门到精通Day02--JavaScript篇
  • k8s部署容器化应用-nginx
  • Linux 根分区爆满排查与解决
  • 南阳东莞网站建设公司天津中冀建设集团有限公司网站
  • linux下虚拟机下安装一个本地yum源
  • SSH密钥认证:从密码到密钥的安全升级指南
  • 企业网站建设平台求职简历模板免费
  • 设计素材网站月收益php+mysql网站开发...
  • 网站建设实现后台数据导出excel公司网站数据库表设计
  • Docker Compose、私有镜像站和Swam集群