计算组合数的三种方式
文章目录
- 摘要
- 递推
- 预处理阶乘
- 乘法逆元
- 乘法逆元的计算方法:
- 费马小定理:
- 卢卡斯定理求组合数
摘要
本文主要介绍计算组合数的三种方式,这三种方式分别适用于不同的数据范围,其中涉及到了数论中的乘法逆元和费马小定理,卢卡斯定理。
递推
当数据范围小于等于300030003000左右时,可直接根据组合数的一个性质:C(n,m)=C(n−1,m)+C(n−1,m−1)C(n, m) = C(n-1,m) + C(n-1, m-1)C(n,m)=C(n−1,m)+C(n−1,m−1)直接递推计算,时间复杂度为O(n2)O(n^2)O(n2).
public static void get_C(int[][] arr){for(int i = 1; i <= MAXN; i++){for(int j = 1; j <= MAXN; j++){if(j == 1) arr[i][j] = i;else arr[i][j] = (arr[i-1][j] + arr[i-1][j-1])%1000000007;}}
}
预处理阶乘
当数据范围小于等于10510^5105左右时,我们要采取更快的方式计算组合数。
组合数公式:C(n,m)=C(n, m) =C(n,m)= n!m!(n−m)!{n!}\over{m!(n-m)!}m!(n−m)!n!
如果我们可以预处理出所有阶乘,然后根据这个公式就可以在O(1)O(1)O(1)的时间复杂度内求出组合数,预处理所有阶乘的时间复杂度是O(n)O(n)O(n), 所以总的时间复杂度就是O(n)O(n)O(n)。
但是有一点需要注意的是:当结果很大需要对阶乘取模时,除法并不满足取模分配律。
也就是说: (n!m!(n−m)!{n!}\over{m!(n-m)!}m!(n−m)!n!)%P≠\%P \neq%P=n!%p(m!(n−m)!)%p{n!\%p}\over{(m!(n-m)!)\%p}(m!(n−m)!)%pn!%p
那怎么办呢,我们既然必须要对其取模,还必须要用阶乘,不用除法怎么算呢?这要感谢伟大的前人发现了乘法逆元这个神奇的东西。可以将除法取模转化为乘法取模,乘法是满足取模分配律的。
那么什么是乘法逆元呢?
乘法逆元
对于一个整数a,如果a∗b≡1(moda*b≡1(moda∗b≡1(mod p)p)p),则在模ppp的意义下:
a是b的逆元,b是a的逆元a是b的逆元,b是a的逆元a是b的逆元,b是a的逆元。但此条件成立的前提是a,p互质,即gcd(a,p)=1a,p互质,即gcd(a,p)=1a,p互质,即gcd(a,p)=1
这里暂且不说乘法逆元如何证明,只需要知道它怎么求,怎么用就可以了。
乘法逆元的计算方法:
乘法逆元有好几种求法,这里只介绍用费马小定理求逆元。
费马小定理:
如果aaa和ppp互质,则有:ap−1≡1(moda^{p-1}≡ 1 ( modap−1≡1(mod p)p )p)
这个式子是不是和乘法逆元的式子特别相似:
a∗b≡1(moda*b≡1(moda∗b≡1(mod p)p)p)
ap−1≡1(moda^{p-1}≡ 1 ( modap−1≡1(mod p)p )p)
对于两个式子,就差了一个bbb。
我们可以将ap−1≡1(moda^{p-1}≡ 1 ( modap−1≡1(mod p)p )p)转化为:
a∗ap−2≡1(moda*a^{p-2}≡ 1 ( moda∗ap−2≡1(mod p)p )p)
这样,我们就得到了aaa模ppp的乘法逆元就是ap−2a^{p-2}ap−2。我们可以用快速幂来求ap−2a^{p-2}ap−2
好了,知道了乘法逆元怎么用,怎么求,我们就可以来计算 n!m!(n−m)!{n!}\over{m!(n-m)!}m!(n−m)!n!%p\%p%p 了。
对于n!m!(n−m)!{n!}\over{m!(n-m)!}m!(n−m)!n!%p\%p%p,我们可以预处理出所有阶乘,和所有阶乘%p的逆元。
代码:
public static long qmi(long a, long b, long p){ // 快速幂求逆元long res = 1;while(b != 0){if((b&1) == 1){res = res * a % p; }b >>= 1;a = a * a % p;}return res;
}public static void Init(){infact[0] = 1; // 存储逆元fact[0] = 1;for(int i = 1; i <= 100000; i++){fact[i] = fact[i-1] * i % mod; infact[i] = infact[i-1] * qmi(i, mod - 2, mod) % mod;}
}
对于 infact[i] = infact[i-1] * qmi(i, mod - 2, mod) % mod;
这行代码可能有些同学会有点迷,下面举个例子就能懂了。
例如:
(3∗4)p−2%p=3p−2%p∗4p−2%p(3*4)^{p-2} \% p=3^{p-2}\%p*4^{p-2}\%p(3∗4)p−2%p=3p−2%p∗4p−2%p
所以,上述代码显然是成立的。
然后预处理完所有的阶乘和阶乘的逆元之后,就可以直接求组合数了。
设一共有n组数据,求每组数据的组合数。完整代码:
import java.io.*;
import java.util.*;public class Main{static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));static final int N = 100005, mod = 1000000007;static int n;static long[] fact = new long[N];static long[] infact = new long[N];public static int Int(String s){return Integer.parseInt(s);} public static long qmi(long a, long b, long p){long res = 1;while(b != 0){if((b&1) == 1){res = res * a % p; }b >>= 1;a = a * a % p;}return res;}public static void Init(){infact[0] = 1;fact[0] = 1;for(int i = 1; i <= 100000; i++){fact[i] = fact[i-1] * i % mod; infact[i] = infact[i-1] * qmi(i, mod - 2, mod) % mod;}}public static void main(String[] args)throws Exception{n = Int(in.readLine());Init();for(int i = 0; i < n; i++){String[] s = in.readLine().split(" ");int a = Int(s[0]);int b = Int(s[1]);out.write((((long)fact[a] * infact[b] % mod * infact[a-b]%mod)%mod) + "\n");}out.flush();}
}
卢卡斯定理求组合数
如果数据范围非常大,在101810^{18}1018内的话,用O(n)的方式也会超时了,我们需要另辟蹊径,伟大的前人又发现了一个神奇的东西:卢卡斯定理。
卢卡斯定理:C(n,m)%p=C(n/p,m/p)∗C(n%p,m%p)%pC(n,m)\%p=C(n/p,m/p)*C(n\%p,m\%p)\%pC(n,m)%p=C(n/p,m/p)∗C(n%p,m%p)%p
时间复杂度是:O(logp(n)∗p),p必须是质数O(log_p(n)*p),p必须是质数O(logp(n)∗p),p必须是质数
所以当n,mn,mn,m小于p时,我们就直接根据n!m!(n−m)!{n!}\over{m!(n-m)!}m!(n−m)!n!%p\%p%p并利用乘法逆元直接计算组合数的值,当n,mn,mn,m大于p时我们就递归计算C(n/p,m/p)∗C(n%p,m%p)%pC(n/p,m/p)*C(n\%p,m\%p)\%pC(n/p,m/p)∗C(n%p,m%p)%p的值
代码:
import java.io.*;
import java.util.*;public class Main{static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));static final int N = 100005, mod = 1000000007;static int n;static long[] fact = new long[N];static long[] infact = new long[N];public static long Int(String s){return Long.valueOf(s);} public static long qmi(long a, long b, long p){long res = 1;while(b != 0){if((b&1) == 1){res = res * a % p; }b >>= 1;a = a * a % p;}return res;}public static long C(long a, long b, long p){long res = 1;for(long i = 1, j = a; i <= b; i++, j--){res = res * j % p;res = res * qmi(i, p-2, p) % p;}return res;}public static long lucas(long a, long b, long p){if(a < p && b < p){return C(a, b, p);}else return C(a%p, b%p, p) * lucas(a/p, b/p, p) % p;}public static void main(String[] args)throws Exception{n = (int)Int(in.readLine());for(int i = 0; i < n; i++){String[] s = in.readLine().split(" ");long a = Int(s[0]);long b = Int(s[1]);long p = Int(s[2]);out.write(lucas(a, b, p) + "\n");}out.flush();}
}
计算组合数的三种方式相关推荐
- java计算时间差(耗时计算)的三种方式
目录 一.System.currentTimeMillis() 二.StopWatch 1.spring 用法 ①.简单用法 ②.说明 ③.方法 ④.详细用法 2.apache 用法 ①.简单用法 ② ...
- 含根式的定积分计算_三种方式计算不定积分∫x√(x+1)dx。
原标题:三种方式计算不定积分∫x√(x+1)dx. 主要内容: 通过根式换元.分项凑分以及分部积分法等相关知识,介绍不定积分∫x√(x+1)dx的三种计算方法和步骤. 根式换元法: 设√(x+1)=t ...
- 相似度计算的三种方式
相似度计算的三种方式 欧几里德评价 欧几里得度量(euclidean metric)(也称欧式距离)是一个通常采用的距离定义,指在m维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离 ...
- 日常生活中怎样利用计算机的,数据存储与管理在日常生活中的三种方式
数据存储与管理在使用的时候还是有很多的技术性要求需要我们不断的学习和实践.下面是在长时间的市场积累中总结出来的一些关于数据存储与管理的技巧. 过去20年里,计算领域发生了很大的变化,无论如何变化,计算 ...
- Qt三种方式实现FTP上传功能
FTP协议 FTP的中文名称是"文件传输协议",是File Transfer Protocol三个英文单词的缩写.FTP协议是TCP/IP协议组中的协议之一,其传输效率非常高,在网 ...
- java 创建线程_【80期】说出Java创建线程的三种方式及对比
点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 >>号外:往期面试题,10篇为一个单位归置到本公众号菜单栏->面试题,有需要的欢迎翻阅. 一.Ja ...
- 图神经网络(一)图信号处理与图卷积神经网络(1)矩阵乘法的三种方式
图神经网络(一)图信号处理与图卷积神经网络(1)矩阵乘法的三种方式 1.1 矩阵乘法的三种方式 参考文献 图信号处理(Graph Signal Processing,GSP) 1是离散信号处理(D ...
- Spring 容器:三种方式解决 Resource leak: ‘applicationContext‘ is never closed 问题
文章目录 前言 一.Spring 容器警告的产生 1.1.项目场景 二.Spring 容器未关闭后果分析 2.1.肉眼可见的警告 2.2.导致的内存泄漏 2.2.1.什么是内存泄漏? 2.2.2.如何 ...
- PHP数组缓存:三种方式JSON、序列化和var_export的比较
使用PHP的站点系统,在面对大数据量的时候不得不引入缓存机制.有一种简单有效的办法是将PHP的对象缓存到文件里.下面我来对这3种缓存方法进行说明和比较. 第一种方法:JSON JSON缓存变量的方式主 ...
- Python---试除法求质数的三种方式对比
此三种方法都是基于试除法,即不断地尝试能否整除.比如要判断自然数 x 是否质数,就不断尝试小于 x 且大于1的自然数,只要有一个能整除,则 x 是合数:否则,x 是质数. 方式1:从 2 一直尝试到 ...
最新文章
- 如果计算机语言是中国人发明的 | 每日趣闻
- Session的异常
- java变量命名规则_变量的概念和声明
- 4.3.2模拟匹配的一种改价算法(KMP及KMP优化算法)
- json 和 数组的区别
- Atitit 提升开发效率 简化设计工具箱 vs 问题诊断 目录 1. 语言类类tool内嵌脚本解释器	1 1.1. 脚本语言 php nodejs python	1 1.2. Sql	1 2. D
- 如何搭建一个vue项目(完整步骤)
- 在linux中访问权限是755,在Linux系统中,一个文件的访问权限是755,其含义是什么?...
- 后台管理系统项目整体流程
- 【Redis】Linux安装Redis步骤详细讲解,以及make、make install区别
- php处理微信weui图片上传
- 树莓派4B WIFI 物理网口设置固定IP方法
- 桌面文件不见了怎么恢复
- 【海康威视单个摄像头读取视频流并保存本地】
- Redhat更换yum源
- 城市交通拥堵问题matlab,深圳市交通拥堵问题分析---数学建模论文.doc
- rtmp直播协议介绍
- 【二】、Linux中mysql的安装并用Navicat连接
- 用Python并行处理大文件,看这篇就够了!
- 学会这120个PS技巧,让你的设计牛起来!