组合数取模在ACM竞赛中是一个很重要的问题,很多选手因为数据太大而束手无策,今天就来详细讲解它。
组合数取模就是求的值,当然根据,和的取值范围不同,采取的方法也不一样。
接下来,我们来学习一些常见的取值情况
(1)和
这个问题比较简单,组合数的计算可以靠杨辉三角,那么由于和的范围小,直接两层循环即可。
(2)和,并且是素数
这个问题有个叫做Lucas的定理,定理描述是,如果
那么得到
这样然后分别求,采用逆元计算即可。
题目:http://acm.fzu.edu.cn/problem.php?pid=2020
题意:求,其中,并且是素数。
代码:
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; typedef long long LL; LL n,m,p; LL quick_mod(LL a, LL b) { LL ans = 1; a %= p; while(b) { if(b & 1) { ans = ans * a % p; b--; } b >>= 1; a = a * a % p; } return ans; } 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) % p; LL b = i % p; ans = ans * (a * quick_mod(b, p-2) % p) % p; } return ans; } LL Lucas(LL n, LL m) { if(m == 0) return 1; return C(n % p, m % p) * Lucas(n / p, m / p) % p; } int main() { int T; scanf("%d", &T); while(T--) { scanf("%I64d%I64d%I64d", &n, &m, &p); printf("%I64d ", Lucas(n,m)); } return 0; }
这是杨辉三角求组合数的代码:
C(n+1,i)=C(n,i)+C(n,i-1)
由杨辉三角推出
void init(long long n,long long m) { long long i,j; memset(c,0,sizeof(c)); for(i=0;i<=m;i++) c[0][i]=c[1][i]=1; for(i=0;i<=m;i++) c[i][i]=1; for(i=0;i<=n;i++) c[i][0]=1; for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { if(i!=j) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod; } } }