数学之组合数

组合数

P885 求组合数 I

类似动态规划
C a b = C a − 1 b − 1 + C a − 1 b C_a^b=C_{a-1}^{b-1}+C_{a-1}^b Cab=Ca1b1+Ca1b
从a个苹果选b个苹果的方案数可以递推出来
数据范围:
1≤n≤10000 询问次数
1≤b≤a≤2000
MOD= 1 0 9 10^9 109+7
如果暴力,时间复杂度为O(10000*2000)=O( 2 ⋅ 1 0 7 2\cdot 10^7 2107)
观察发现构成a,b的组合最多 200 0 2 2000^2 20002种(a2000种,b2000种),于是我们可以在O( 200 0 2 2000^2 20002)预先处理出来

#include<iostream>
#include<algorithm>
using namespace std;
const int N=2010,mod=1e9+7;
int c[N][N];
int n;
void init(){
	for(int i=0;i<N;i++){
		for(int j=0;j<=i;j++){
			if(!j) c[i][j]=1;//从i个苹果里选0个 
			else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
		}
	}
}
int main(){
	scanf("%d",&n);
	init();
	while(n--){
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",c[a][b]);
	}
	return 0;
} 

P886 求组合数 II

数据范围:
1≤n≤10000 询问次数
1≤b≤a≤ 1 0 5 10^5 105
MOD= 1 0 9 10^9 109+7
C a b = a ! ( a − b ) ! ⋅ b ! \bm C_a^b=\frac{a!}{(a-b)!\cdot b!} Cab=(ab)!b!a!
因为有除法,涉及取余时要求逆元转为乘法,fact[i]记录i的阶乘,infact[i]记录i的阶乘mod MOD的逆元
所以表达式变为 C a b C_a^b Cab=fact[a] ⋅ \cdot infact[a-b] ⋅ \cdot infact[b]
因为a,b都与MOD互质,所以存在逆元(快速幂)
时间复杂度O(a ⋅ \cdot log MOD)

#include<iostream>
using namespace std;
typedef long long LL;
int n;
const int N=1e5+10,mod=1e9+7;
int fact[N],infact[N];
int qmi(int a,int k,int p){
	int res=1;
	while(k){
		if(k&1) res=(LL)res*a%mod;
		 a=(LL)a*a%mod;
		 k>>=1;
	}
	return res;
}
int main(){
	fact[0]=infact[0]=1;
	for(int i=1;i<N;i++){
		fact[i]=(LL)fact[i-1]*i%mod; //本行时间复杂度为O(1) 
		infact[i]=(LL)infact[i-1]*qmi(i,mod-2,mod)%mod;//本行时间复杂度为O(logMOD) 
	} //所以整个预处理时间复杂度为O(a*(1+logMOD))=O(a*logMOD); 
	scanf("%d",&n);
	while(n--){
		int a,b;
		scanf("%d%d",&a,&b);
		int t=(LL)fact[a]%mod*infact[a-b]%mod*infact[b]%mod;
		printf("%d\n",t);
	}
	return 0;
}

P887 求组合数 III

数据范围:
1≤n≤20
1≤b≤a≤ 1 0 18 10^{18} 1018
1≤MOD≤ 1 0 5 10^5 105 题目保证MOD为质数
此时MOD比a和b小,不能保证逆元一定存在了(有可能是MOD的倍数),于是引入了lucas定理:
C a b ≡ C a m o d p b m o d p ⋅ C a / p b / p (   m o d   p ) \bm C_a^b\equiv C_{amod p}^{b mod p}\cdot C_{a/p}^{b/p}(\bmod p) CabCamodpbmodpCa/pb/p(modp)
时间复杂度:O( l o g p a ⋅ p ⋅ l o g p log_p^a\cdot p\cdot log p logpaplogp)
分析:

#include<iostream>
using namespace std;
typedef long long LL;
int qmi(int a,int k,int p){
	int res=1;
	while(k){
		if(k&1) res=(LL)res*a%p;
		a=(LL)a*a%p;
		k>>=1;
	}
	return res;
}
int C(int a,int b,int p){
	if(b>a) return 0;
	int res=1;
	//根据定义求Cab 
	for(int i=1,j=a;i<=b;i++,j--){
		res=(LL)res*j%p; //计算分子a*(a-1)*(a-2)···*(a-(b-1)) 
		res=(LL)res*qmi(i,p-2,p)%p;//计算分母,但要用逆元 
	}
	return res;
}
int lucas(LL a,LL b,int p){
	if(a<p&&b<p) return C(a,b,p);
	return (LL)C(a%p,b%p,p)*lucas(a/p,b/p,p)%p;
	//a%p和b%p一定比p小,但a/p和b/p不一定 
}
int main(){
	int n;
	scanf("%d",&n);
	while(n--){
		LL a,b;
		int p;
		scanf("%lld%lld%d",&a,&b,&p);
		printf("%d\n",lucas(a,b,p));
	}
	return 0;
}

P888 求组合数 IV

高精度
根据定义 C a b = a ! ( a − b ) ! ⋅ b ! C_a^b=\frac{a!}{(a-b)!\cdot b!} Cab=(ab)!b!a!,所以我们把 C a b C_a^b Cab分解质因数,方法为先分解 a ! a! a! ( a − b ) ! (a-b)! (ab)! b ! b! b!,然后对于相同的质因数,用分子的次数减分母的次数,结果就是答案的次数
求a!里含质因子p的个数公式: ⌊ a ! p ⌋ + ⌊ a ! p 2 ⌋ + ⌊ a ! p 3 ⌋ + ⌊ a ! p 4 ⌋ + ⌊ a ! p 5 ⌋ + ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ \lfloor \frac{a!}{p} \rfloor+\lfloor \frac{a!}{p^2} \rfloor+\lfloor \frac{a!}{p^3} \rfloor+\lfloor \frac{a!}{p^4} \rfloor+\lfloor \frac{a!}{p^5} \rfloor+······ pa!+p2a!+p3a!+p4a!+p5a!+
时间复杂度:O(log p)

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N=5010;
int primes[N],cnt;
bool st[N];
int sum[N];
void get_primes(int n){ //线性筛 
	for(int i=2;i<=n;i++){
		if(!st[i]) primes[cnt++]=i;
		for(int j=0;primes[j]<=n/i;j++){
			st[primes[j]*i]=true;
			if(i%primes[j]==0) break;
		}
	}
}
vector<int> mul(vector<int> a,int b){
	vector<int> c;
	int t=0;
	for(int i=0;i<a.size();i++){
		t+=a[i]*b;
		c.push_back(t%10);
		t/=10;
	}
	//用循环是因为每一位a都会乘以一个整数b,所以最后结果可能出现多位 
	while(t){ 
		c.push_back(t%10);
		t/=10;
	}
	//不用处理前导零,因为这题不可能出现乘0
	return c;
}
int get(int n,int p){ //求n!中包含p的次数
	int res=0;
	while(n){
		res+=n/p;
		n/=p;
	}
	return res;
}
int main(){
	int a,b;
	scanf("%d%d",&a,&b);
	get_primes(a);
	for(int i=0;i<cnt;i++){
		int p=primes[i];
		//把分子和分母相约,剩下的就是最终答案 
		sum[i]=get(a,p)-get(b,p)-get(a-b,p);//记录primes[i]一共要乘多少次 
	}
	vector<int> res;
	res.push_back(1);
	for(int i=0;i<cnt;i++){
		for(int j=0;j<sum[i];j++){
			res=mul(res,primes[i]); 
		}
	}
	for(int i=res.size()-1;i>=0;i--){
		printf("%d",res[i]);
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值