快速求组合数

article/2025/9/23 17:45:14

摘自https://www.jianshu.com/p/718a5ac26238
逆元+快速幂解法
(一)基本概念
上面两种方法都使用了递归方法,递归方法有个缺陷,就是在数据较大时效率较低。所以这里要介绍一个种新的求组合算法。在了解此算法之前,要先了解一些概念。
1 同余
同余是数论中的重要概念。
给定一个正整数m,如果两个整数a和b满足a-b能够被m整除,即(a-b)/m得到一个整数,那么就称整数a与b对模m同余,记作a≡b(mod m)
例1:4 ≡ 9 (mod 5),即4和9对模5同余
例2:13 ≡ 23(mod 10),即13和23对模10同余

2 模的加减乘除运算
取模运算的等价变形适合加法、减法、乘法
(a + b) % p = (a % p + b % p) % p
(a - b) % p = (a % p - b % p) % p
(a * b) % p = (a % p * b % p) % p
例3:(30 + 40) % 11 = 70 % 11 = 4
(30% 11 + 40%11) % 11 = (8 + 7) % 11 = 15 % 11 = 4
例4:(80 - 20) % 7 = 60 % 7 = 4
(80 % 7 - 20 % 7) % 7 = (3 - 6) % 7 = -3 % 7 = 4 (取模是让商尽可能小,所以这里有 -3 / 7 = -1 …… 4)
例5:(18 * 20) % 7 = 360 % 7 = 3
(18%7 * 20%7)% 7 = (4 * 6)% 7 = 3
但是,取模运算的等价变形不符合除法
a/b % p ≠ (a%p / b%p) % p
例6:(100 % 20)% 11 = 5 % 11 = 5
(100%11 / 20%11) % 11 = (1 / 9) % 11 = 0 % 11 = 0
3 逆元
逆元:对于a和p,若gcd(a, p) = 1(a和p互素)且 ab%p≡1,则称b为a%p的逆元。
那这个逆元有什么用呢?试想一下求(a / b)%p,如果你知道b%p的逆元是c,那么就可以转变成(a/b)%p = (a/b) * 1 % p = (a / b) * (b
c % p) % p = a*c % p = (a%p) (c%p) % p,这样的话,除法运算就可以转化为乘法运算。
那怎么求逆元呢?这时候就要引入强大的费马小定理!

费马小定理:对于a和素数p,满足a^(p-1) % p ≡ 1

接着因为a^(p−1) = a^(p−2) * a,所以有a^(p−2) * a % p ≡ 1。
对比逆元的定义可得,a^(p−2)就是a的逆元。
所以问题就转换成求解a^(p−2),即变成求快速幂的问题了。
4 快速幂
这部分的内容可以参考 小朋友学算法(6):求幂pow函数的四种实现方式 中的第四种方法
(二)逆元 + 快速幂求组合思路
现在目标是求C(n, m) %p,p为素数(经典p=1e9+7)。
虽然有C(n, m) = n! / [m! (n - m)!],但由于取模的性质对于除法不适用,则有

在这里插入图片描述

所以需要利用逆元把“除法”转换成“乘法”,才能借助取模的性质计算组合数。
求解C(n, m)%p的步骤:
(1)通过循环,预先算好所有小于max_number的阶乘(%p)的结果,存到fac[max_number]里 (fac[i] = i! % p)
(2)求m! % p的逆元(即求fac[m]的逆元):根据费马小定理,x%p的逆元为x^(p−2), 因此通过快速幂,求解fac[m]^(p−2) % p,记为M
(3)求(n-m)! % p的逆元:同理就是求解fac[n−m]^(p−2) % p,记为NM
(4)C(n, m) % p = ((fac[n] * M) % p * NM) % p

具体代码如下:

#include <cstdio>
using namespace std;#define MAX_NUMBER 100000
//快速幂求x^n%mod
long long quick_pow(long long x, long long n, long long mod)
{long long res = 1;while (n){if (n & 1){res = res * x % mod;}x = x * x % mod;n >>= 1;}return res;
}long long fac[MAX_NUMBER+5];
long long n, m, p;int main()
{while (~scanf("%lld %lld %lld", &n, &m, &p)){//预处理求fac,fac[i] = i!%pfac[0] = 1;for (int i = 1; i <= n; i++){fac[i] = fac[i - 1] * i % p;}//组合数 = n!*(m!%p的逆元)*((n-m)!%p的逆元)%pprintf("%lld\n", fac[n] * quick_pow(fac[m], p - 2, p) % p * quick_pow(fac[n - m], p - 2, p) % p);}
}

http://chatgpt.dhexx.cn/article/TRQ0kbI9.shtml

相关文章

Java计算组合数以及生成组合排列

前言 组合数计算 公式法 逐个相除法(错误) 逐个相除法修正版 素数幂乘法 基本公式法 平方差连乘法 组合恒等法 简单递归法 杨辉三角法 杨辉三角优化法 二进制法 组合数计算小结 获取数组的组合排列 二进制法 基本迭代法 从后向前迭代法&#xff08;Matlab版本…

[组合] 组合数计算四大算法模板(模板+卢卡斯定理)

文章目录 0. 前言1. 预处理组合数组合递推式2. 预处理阶乘逆元3. 卢卡斯定理4. 高精度组合数 0. 前言 组合数求解有很多种方式&#xff0c;不同的方式对应这不同的时间复杂度&#xff0c;难以程度也是不尽相同。根据数据范围选择对应的方法即可。 1. 预处理组合数组合递推式 …

组合数

1、定义&#xff1a;从m个不同元素中&#xff0c;任取n(n≤m)个元素并成一组&#xff0c;叫做从m个不同元素中取出n个元素的一个组合&#xff1b;从m个不同元素中取出n(n≤m)个元素的所有组合的个数&#xff0c;叫做从m个不同元素中取出n个元素的组合数。 2、公式&#xff1a;…

组合数取模总结

题目描述&#xff1a; 在ACM竞赛现在叫JB竞赛&#xff1f;中&#xff0c;经常会遇到组合数取模的题目&#xff1b;就我现在的水平而言&#xff0c;大概分为以下三类&#xff0c;以后遇到新的方法会在做补充&#xff1b; 第一种&#xff1a; n和m都较小 &#xff08;<1000&a…

逆元求组合数

逆元简介 同余符号 ≡ 先bb一下 ≡&#xff0c;这个符号有三个意思&#xff0c;再这里用到的意思为“同余符号”。≡ 的介绍 两个整数a&#xff0c;b&#xff0c;若它们除以整数m所得的余数相等&#xff0c;则称a&#xff0c;b对于模m同余 记作a≡b(mod m) 读作a同余于b模m&am…

组合数(Combinatorial_Number)

定义&#xff1a; 从n个不同元素中&#xff0c;任取m(m≤n)个元素并成一组&#xff0c;叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数&#xff0c;叫做从n个不同元素中取出m个元素的组合数。 公式&#xff1a; 在线性写法中被…

组合数的计算

本篇博客来自南昌理工学院acm集训队成员yyj 组合数 1.定义 组合数&#xff1a;从 n 个不同元素中每次取出 m 个不同元素 &#xff0c;不管其顺序合成一组&#xff0c;称为从 n 个元素中不重复地选取 m 个元素的一个组合。所有这样的组合的种数称为组合数。 2.性质与描述 2…

组合数的性质证明

性质 极其简单的证明 根据杨辉三角的性质&#xff0c;第x行第y个的值为C(x-1,y-1) 那么 一目了然。 严肃的证明

组合数性质--二项式系数之和等于2^n的证明

1.公式 首先我们都知道组合数的意义&#xff0c;就是说一共有n个样本&#xff0c;一次性从中取出m个样本&#xff0c;一共有多少种不同的取法。它的公式如下&#xff1a; 它有这么一个性质&#xff1a; 该性质有若干种证明方式&#xff0c;今天我在这边写出我觉得挺巧妙的一种…

组合数原理

资料来源&#xff1a;https://baike.baidu.com/ 组合数 从n个不同元素中&#xff0c;任取m(m≤n)个元素并成一组&#xff0c;叫做从n个不同元素中取出m个元素的一个组合&#xff1b;从n个不同元素中取出m(m≤n)个元素的所有组合的个数&#xff0c;叫做从n个不同元素中取出m个…

深入详细理解矩阵 (矩阵的加减乘、转置、共轭、共轭转置)

简介 矩阵:英文名Matrix。在数学名词中&#xff0c;矩阵用来表示统计数据等方面的各种有关联的数据。这个定义很好地解释了Matrix代码制造世界的数学逻辑基础。矩阵是数学中最重要的基本概念之一&#xff0c;是代数学的一个主要研究对象&#xff0c;也是数学研究及应用的一个重…

【线性代数之二】矩阵与行列式

一、矩阵 1.1 定义 由 m n 个数aij排成的m行n列的数表称为m行n列的矩阵&#xff0c;简称m n矩阵。记作&#xff1a; 这mn 个数称为矩阵A的元素&#xff0c;简称为元&#xff0c;数aij位于矩阵A的第i行第j列&#xff0c;称为矩阵A的(i,j)元&#xff0c;以数 aij为(i,j)元的…

CString类型转换为LPCSTR类型

今天编程遇到一个问题&#xff0c;就是openGL中某个函数需要传入LPCSTR类型的参数&#xff0c;而通过MFC对话框获取得到的是CString类型的参数&#xff0c;因此需要将CString转化为LPCSTR类型&#xff0c;网上有很多这样的强转类型&#xff0c;然而却发现在强转的时候没有用&am…

linux jstat 简介

本文目录一览&#xff1a; 1、Linux使用jstat命令查看jvm的GC情况2、linux怎么监控 jvm内存 jstat3、Linux系统监控要用到哪些命令4、linux上如何安装jstatd服务 Linux使用jstat命令查看jvm的GC情况 Linux 使用jstat命令查看jvm的GC情况 命令格式 jstat命令命令格式&#…

内存屏障与volatile(C语言版)

内存屏障与volatile&#xff08;C语言版&#xff09; 最有价值的写在最前面 内存屏障与volatile是高并发编程中比较常用的两个技术&#xff0c;无锁队列的时候就会用到这两项技术。然而这两项技术设计比较广的基础知识&#xff0c;所以比较难以理解&#xff0c;也比较不容易解…

WDM驱动

WDM英文Windows Driver Model(WDM)的缩写。 简介 WDM WDM是WINDOWS2000认证的驱动程序&#xff0c;WIN2000由NT发展而来&#xff0c;所以对于设备的支持功能有限&#xff0c;同时为了最大限度的保障稳定性&#xff0c;所以推崇WDM驱动&#xff0c;但同时WDM驱动也就是功能最少…

快速转换dBm与W

dBm是一个表示功率绝对值的值&#xff08;也可以认为是以1mW功率为基准的一个比值&#xff09;&#xff0c;计算公式为&#xff1a;10log&#xff08;功率值/1mw&#xff09;。 这里我们介绍一种将dBm转换为W的口算方法&#xff0c;这一方法总结起来就是 “1个基准”和“2个原则…