组合数计算总结
2015-02-04 13:40
351 查看
一、 一般组合数计算
利用
![](http://latex.numberempire.com/render?C_n%5Em&sig=4daed4e493b740582452a8f63523e8ca)
的据算公式进行运算,简单明了。利用乘法与除法的同时运行,有效地降低溢出(除法能整除的保证:每i个数中必有一个是i的倍数)。只能对最后的结果进行取模,无法在运算时取模,虽然求取的时候转化为了ans* ans’ * ans’’……的形式,虽然连乘运算可以逐步取模,但如果对ans取模,将无法保证下一步的除法能整除,所以不可取。
时间复杂度:O(m)
mod的要求:无
有效范围:1<=n,m<=60
——————————————————————————————————
Ps:ans *= (n – m + i)也可用ans *= (n – I + 1),但前者更小
二、 杨辉三角计算组合数
利用杨辉三角与组合数之间的关系进行逐步求解组合数,弥补了中间结果无法取模的缺点,但时间复杂度高。可以逐步取模,无溢出。
时间复杂度:O(n*m)
mod的要求:无
有效范围:1<=n,m<=1000
——————————————————————————————————
三、 利用乘法逆元求组合数
逆元定义:a*b=1,则称b为a的乘法逆元。
而在同一个mod下,可以使a的逆元为一个整数,a*b%mod ≡1,则b为a在mod下的乘法逆元。由此,可将 计算公式转化为全为乘法的式子,如此,便可以实现逐步取模。
由费马小定理知,p是质数,且Gcd(a,p)=1,那么a^(p-1) ≡ 1(modp),因此易知a*a^(p-2)= 1(mod p),则,a^(p-1)即为a在p下的乘法逆元。
时间复杂度:O(m*log(m))
mod的要求:mod为素数,且m<mod或GCD(mod, m)=1
有效范围:1<=n,m<=10^6
——————————————————————————————————
四、 分解因子求组合数
![](http://latex.numberempire.com/render?C_n%5Em%3Dn%21%2F%28%28n-m%29%21%2Am%21%29&sig=cc9b39cbd8595bcd33f823bfe5ee4b9d)
,利用同底数的指数的除法即指数相减的原理讲式子转化为乘法,从而实现逐步取模。即求出n!,(n-m)!,m!中的所有素数因子并计算。
时间复杂度:O(n)
mod的要求:无(可为合数)
有效范围:1<=n,m,p<=10^6
——————————————————————————————————
五、 Lucas定理*
Lucas定理:如果p为素数,且
![](http://latex.numberempire.com/render?n%3Dn_k%20p%5Ek%2B%7Bn%7D_%7B%28k-1%29%7D%20%7Bp%7D%5E%7B%28k-1%29%7D%2B...%2Bn_1%20p%5E1%2Bn_0&sig=51c133d3a3893aa03c50c2e78279a717)
![](http://latex.numberempire.com/render?m%3Dm_k%20p%5Ek%2B%7Bm%7D_%7B%28k-1%29%7D%20%7Bp%7D%5E%7B%28k-1%29%7D%2B...%2Bm_1%20p%5E1%2Bm_0&sig=7d7e6d1d20ade2e9ec985df38f5baa82)
那么
![](http://latex.numberempire.com/render?C_n%5Em%3D%5Cprod_%7Bi%3D0%7D%5E%7Bk%7DC_%7Bn_i%7D%5E%7Bm_i%20%7D%5C%3B%20%28mod%5C%3B%20p%29&sig=b482dbd29a878b382b56fbcc51e7d536)
由此定理,再结合乘法逆元就可以实现在逐步取模的基础上将计算量大大减少,且可以保证所有的 ,即 与p互质,从而避免了乘法逆元不存在的特殊情况。此方法在p较小的时候能发挥很大的作用。
时间复杂度:
mod的要求:素数
有效范围:1<=n,m,p<=10^9
——————————————————————————————————
半预处理型
由于Lucas定理保证了阶乘的数均小于p,所以可以讲所有的阶乘先预处理,优化C(n,m)
mod的要求:p<10^6,且为素数
有效范围:1<=n,m<=10^9
——————————————————————————————————
全预处理型
若数据组数较多,且p较小(<10^4),则可以预处理所有可能的阶乘并进行计算
mod的要求:p<10^4,且为素数
有效范围:1<=n,m<=10^9
——————————————————————————————————
利用
的据算公式进行运算,简单明了。利用乘法与除法的同时运行,有效地降低溢出(除法能整除的保证:每i个数中必有一个是i的倍数)。只能对最后的结果进行取模,无法在运算时取模,虽然求取的时候转化为了ans* ans’ * ans’’……的形式,虽然连乘运算可以逐步取模,但如果对ans取模,将无法保证下一步的除法能整除,所以不可取。
时间复杂度:O(m)
mod的要求:无
有效范围:1<=n,m<=60
——————————————————————————————————
//一般方法求C(n,m)最后取模。C(62,28)溢出。有效范围1<n,m<=60 ll CNormal(int n, int m) { if ( m>n ) return 0; ll ans = 1; for (int i=1; i<=m; i++) { ans *= (n – m + i); ans /= i; } return ans % mod; }
Ps:ans *= (n – m + i)也可用ans *= (n – I + 1),但前者更小
二、 杨辉三角计算组合数
利用杨辉三角与组合数之间的关系进行逐步求解组合数,弥补了中间结果无法取模的缺点,但时间复杂度高。可以逐步取模,无溢出。
时间复杂度:O(n*m)
mod的要求:无
有效范围:1<=n,m<=1000
——————————————————————————————————
//杨辉三角求C(n,m) ll matrix[110][110]={0}; ll CMatrix(int n, int m) { if ( m>n ) return 0; ll ans = 0; matrix[1][0] = matrix[1][1] = 1; for (int i=2; i<=n; i++) { int k = min(i, m); for(int j=0; j<=k; j++) { if ( j==0 || j==i ) matrix[i][j] = 1; else matrix[i][j] = (matrix[i-1][j] + matrix[i-1][j-1]) % mod; } } return matrix [m]; }
三、 利用乘法逆元求组合数
逆元定义:a*b=1,则称b为a的乘法逆元。
而在同一个mod下,可以使a的逆元为一个整数,a*b%mod ≡1,则b为a在mod下的乘法逆元。由此,可将 计算公式转化为全为乘法的式子,如此,便可以实现逐步取模。
由费马小定理知,p是质数,且Gcd(a,p)=1,那么a^(p-1) ≡ 1(modp),因此易知a*a^(p-2)= 1(mod p),则,a^(p-1)即为a在p下的乘法逆元。
时间复杂度:O(m*log(m))
mod的要求:mod为素数,且m<mod或GCD(mod, m)=1
有效范围:1<=n,m<=10^6
——————————————————————————————————
//获得a在mod下的乘法逆元 ll GetInverse(ll a, ll mod) { ll ans = 1, n = mod - 2; a %= mod; while ( n ) { if ( n&1 ){ ans = ans * a % mod; } n >>= 1; a = a * a % mod; } return ans; } //逆元求C(n,m)%mod ll C(ll n, ll m) { if ( m>n ) return 0; ll ans = 1; for (int i=1; i<=m; i++) { ll a = (n + i - m) % mod; ll b = i % mod; ans = ans * (a * GetInverse(b, mod) % mod) % mod; } return ans; }
四、 分解因子求组合数
,利用同底数的指数的除法即指数相减的原理讲式子转化为乘法,从而实现逐步取模。即求出n!,(n-m)!,m!中的所有素数因子并计算。
时间复杂度:O(n)
mod的要求:无(可为合数)
有效范围:1<=n,m,p<=10^6
——————————————————————————————————
// 求素数表 int prime[1000000] = {0}; bool isPrime[1000000]; void getPrime(int maxn) { int n = 0; memset(isPrime, true, sizeof(isPrime)); for (int i=2; i<=maxn; i++) { if ( isPrime[i] ) { prime[n++] = i; for (int j=i*2; j<=maxn; j+=i) isPrime[j] = false; } } } //快速幂 long long myPow(long long a,long long b) { long long r=1, base=a % mod; while ( b ) { if ( b&1 ){ r *= base; r %= mod; } base *= base; base %= mod; b >>= 1; } return r; } //获得n!中因子p的个数 ll getPNum(ll n, ll p) { ll ans = 0; while ( n ) { ans += n / p; n /= p; } return ans; } //利用因子求C(n,m) ll CFactor(ll n, ll m) { if ( m>n ) return 0; ll ans = 1; for (int i=0; prime[i]!=0 && prime[i]<=n; i++) { ll a = getPNum(n, prime[i]); ll b = getPNum(m, prime[i]); ll c = getPNum(n - m, prime[i]); a -= (b + c); ans *= myPow(prime[i], a); ans %= mod; } return ans; }
五、 Lucas定理*
Lucas定理:如果p为素数,且
那么
由此定理,再结合乘法逆元就可以实现在逐步取模的基础上将计算量大大减少,且可以保证所有的 ,即 与p互质,从而避免了乘法逆元不存在的特殊情况。此方法在p较小的时候能发挥很大的作用。
时间复杂度:
mod的要求:素数
有效范围:1<=n,m,p<=10^9
——————————————————————————————————
//利用lucas定理求C(n,m) ll CLucas(ll n, ll m) { //if ( m==0 ) return 1; //return C(n % mod, m % mod) * CLucas(n / mod, m / mod) % mod; ll ans = 1; while ( m ) { ans = ans * C(n % mod, m % mod) % mod; n /= mod; m /= mod; } return ans; }
半预处理型
由于Lucas定理保证了阶乘的数均小于p,所以可以讲所有的阶乘先预处理,优化C(n,m)
mod的要求:p<10^6,且为素数
有效范围:1<=n,m<=10^9
——————————————————————————————————
//半预处理 const ll MAXN = 100000; ll fac[MAXN+100]; void init(int mod) { fac[0] = 1; for (int i=1; i<mod; i++){ fac[i] = fac[i-1] * i % mod; } } //半预处理逆元求C(n,m)%mod ll C(ll n, ll m) { if ( m>n ) return 0; return fac * (GetInverse(fac[m]*fac[n-m], mod)) % mod; }
全预处理型
若数据组数较多,且p较小(<10^4),则可以预处理所有可能的阶乘并进行计算
mod的要求:p<10^4,且为素数
有效范围:1<=n,m<=10^9
——————————————————————————————————
//阶乘预处理 int prime[10000+10] = {0}; bool isPrime[10000+10]; int inverse[10000][1250] = {0}; int fac[10000][1250] = {0}; void init(int maxn) { int n = 0; memset(isPrime, true, sizeof(isPrime)); for (int i=2; i<maxn; i++) { if ( isPrime[i] ) { prime[i] = n; for (int j=i*2; j<maxn; j+=i) isPrime[j] = false; //计算逆元 inverse[0] = inverse[1] = 1; for (int j=2; j<i; j++) { inverse[j] = (i - i/j) * inverse[i%j] % i; } //预处理阶乘 fac[0] = fac[1] = 1; for (int j=2; j<i; j++) { fac[j] = j * fac[j-1] % i;//普通阶乘 inverse[j] = inverse[j] * inverse[j-1] % i;//逆元阶乘 } n++; } } } //全预处理求C(n,m)%mod ll C(int n, int m) { if ( m>n ) return 0; int num = prime[i]; return (ll)fac [num] * inverse[m][num] % i * inverse[n-m][num] % i; }
相关文章推荐
- 表达式计算算法总结
- 表达式计算算法总结
- Direct3D中常用的数学计算方法总结
- list滚动条Scroll 偏移和长度计算公式总结
- c/c++在windows下获取时间和计算时间差的几种方法总结
- PMP知识点总结—计算题汇总
- 组合数计算技巧
- 高性能计算学习总结
- 计算组合数C(m,n)
- 表达式计算算法总结
- 软件测试计算公式总结
- 对深圳大学选课系统中的课程学时计算问题总结
- 云计算之总结篇
- MySQL的 时间/日期 计算总结
- ip地址相关计算规律总结
- 组合数计算Com(n,r)
- 一个采购价格及成本计算系统项目的总结
- 并行计算部分总结
- 二分+叉积 apio2011 陈可卿 计算几何的一道简单题 poj2318 兼集训总结
- Firefox下的坐标计算方法总结