组合数取模

组合数取模的应用背景

运行时间限制:1s;
运行空间限制:256M;
代码长度限制:2000000B
题目描述
高一八班有n个人,从1到n编号,一次互判作业时,老师随机将作业发到这n个人手中。已知有k个人拿到的不是自己的作业,那么请问有多少种情况符合条件呢?
输入
共1行,包含2个整数n和k。
输出
共1行,包含1个整数,表示答案。由于答案可能很大,请输出答案模10007的余数。
输入样例
4 3
输出样例
8
测试规模
对于30%的数据,0≤k≤n≤10。
另有10%的数据,k=0。
另有10%的数据,k=1。
对于70%的数据,0≤k≤n≤10000。
对于100%的数据,0≤k≤1000000,1≤n≤1000000000。

组合数 C n m C_n^{m} Cnm的计算

  组合数的计算

组合数取模 C n m % p C_n^{m}\%p Cnm%p的计算

方法一:通过递推公式计算

  该算法可以支持 m ≤ n ≤ 1000 m≤n≤1000 mn1000的情况,对 p p p的大小和素性没有要求, p ≤ 1 0 9 p≤10^9 p109均可。

递归:

int res[1010][1010] = {0};
int C(int n, int m, int p){
	if(m == 0 || m == n) return 1;
	if(res[n][m] != 0) return res[n][m];  //C(n, m)已计算过
	return res[n][m] = C(n - 1, m) + C(n - 1, m - 1) % p;  //先计算,再赋值,并返回
}

递推:

//将底数从2~n的所有组合数的表计算出来
long long res[100][100] = {0};
void calC(int n){
	for(int i = 0; i <= n; i++)
		res[i][0] = res[i][i] = 1;  //初始化边界,组合数C(n, 0) = C(n, n) = 1
	for(int i = 2; i <= n; i++){
		for(int j = 1; j <= i / 2; j++){
			res[i][j] = res[i - 1][j] + res[i - 1][j - 1] % p;  //递推计算C(i, j)
			res[i][i - j] = res[i][j];
		}
	}
}
//直接输出组合数C(n, m)
long long C(long long n, m){
	return res[n][m];
}

方法二:根据定义式计算

  对组合数进行质因子分解,若有 C n m = p 1 c 1 × p 2 c 2 × ⋯ × p k c k C_n^{m}=p_1^{c_1}\times p_2^{c_2}\times\cdots\times p_k^{c_k} Cnm=p1c1×p2c2××pkck,那么 C n m % p = p 1 c 1 × p 2 c 2 × ⋯ × p k c k % p C_n^{m}\%p=p_1^{c_1}\times p_2^{c_2}\times\cdots\times p_k^{c_k}\%p Cnm%p=p1c1×p2c2××pkck%p,于是可以对每组 p i c i % p p_i^{c_i}\%p pici%p,然后相乘取模得到结果。
  如何对 C n m C_n^{m} Cnm进行质因子分解? 考虑到 C n m = n ! m ! ( n − m ) ! C_n^{m}= \frac {n!} {m!(n-m)!} Cnm=m!(nm)!n!,只需遍历不超过n的所有质数 p i p_i pi,然后分别计算 n ! n! n! m ! m! m! ( n − m ) ! (n-m)! (nm)!中含有 p i p_i pi的个数 x x x y y y z z z,即可计算出 C n m C_n^{m} Cnm中质因子 p i p_i pi的个数为 x − y − z x-y-z xyz
  如何求 n ! n! n!中含质因子 p i p_i pi的个数? n ! n! n!中有多少个质因子 p p p
  该方法的时间复杂度为: O ( k l o g n ) O(klogn) O(klogn),其中 k k k为不超过 n n n的质数个数。适用于 m ≤ n ≤ 1 0 6 m\leq n\leq10^6 mn106的数据,对 p p p的大小和素性没有额外要求。

代码如下:

//使用筛法得到素数表prime,由于n可能为素数,故表中最大素数不得小于n
int prime[maxn];

//计算C(n, m)%p
int C(int n, int m, int p){
	int ans = 1;
	//遍历不超过n的所有质数
	for(int i = 0; prime[i] <= n; i++){
		//计算C(n, m)中prime[i]的指数c,cal(n, k)为n!中含质因子k的个数
		int c = cal(n, prime[i]) - cal(m, prime[i]) - cal(n - m, prime[i])  //c = x - y - z
		//快速幂计算prime[i]^c%p
		ans = ans * binaryPow(prime[i], c, p) % p;
	}
	return ans;
}

快速幂运算

方法三:Lucas定理

  如果 p p p是素数,将 m m m n n n表示为 p p p进制:
m = m k p k + m k − 1 p k − 1 + ⋯ + m 0 n = n k p k + n k − 1 p k − 1 + ⋯ + n 0 m=m_kp^k+m_{k-1}p^{k-1}+\cdots+m_0\\ n=n_kp^k+n_{k-1}p^{k-1}+\cdots+n_0 m=mkpk+mk1pk1++m0n=nkpk+nk1pk1++n0那么Lucas定理有: C n m ≡ C n k m k × C n k − 1 m k − 1 × ⋯ × C n 0 m 0 ( m o d p ) C_n^{m}\equiv C_{n_k}^{m_k}\times C_{n_k-1}^{m_k-1}\times\cdots\times C_{n_0}^{m_0}(mod p) CnmCnkmk×Cnk1mk1××Cn0m0(modp)成立。
  时间复杂度为: O ( l o g n ) O(logn) O(logn),适用于 m ≤ n ≤ 1 0 18 m\leq n\leq10^{18} mn1018的数据,且要求 p ≤ 1 0 5 p\leq10^5 p105并同时为素数。

代码如下:

int Lucas(int n, int m){
	if(m == 0) return 1;
	return C(n % p, m % p) * Lucas(n / p, m / p) % p;
}

总结

方法nmp
方法一 n ≤ 1 0 4 n\leq 10^4 n104 m ≤ 1 0 4 m\leq 10^4 m104 p ≤ 1 0 9 p\leq 10^9 p109
方法二 n ≤ 1 0 6 n\leq 10^6 n106 m ≤ 1 0 6 m\leq 10^6 m106 p ≤ 1 0 9 p\leq 10^9 p109
Lucas n ≤ 1 0 18 n\leq 10^{18} n1018 m ≤ 1 0 18 m\leq 10^{18} m1018 p ≤ 1 0 5 p\leq 10^5 p105且为素数

参考资料

[1]. 《算法笔记》P181-190
[2]. 组合数取模的题

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

D-A-X

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值