【acwing】885. 求组合数 (I ~Ⅳ)*

这篇博客介绍了四种不同的计算组合数C_a^b的方法,包括递推式、阶乘除法、卢卡斯定理以及高精度质因数分解。每种方法都有其适用场景和效率考虑,如递推式适用于小规模数据,卢卡斯定理则能处理大数模运算。博客中还提供了相应的C++代码实现,并讨论了内存限制下的优化策略。
摘要由CSDN通过智能技术生成

穿越隧道

组合数 Ⅰ

在这里插入图片描述

递推式:
C a b = C a − 1 b + C a − 1 b − 1 C_a ^b = C_{a-1} ^ b + C_{a-1} ^ {b-1} Cab=Ca1b+Ca1b1
C a 0 = 1 C_a ^ 0 = 1 Ca0=1

#include <iostream>
#include <cstring>
#include <algorithm>
//像极了dp。。
using namespace std;
const int N = 2100, mod = 1e9 + 7;
int c[N][N];
void init(){
	for(int i = 0; i < N; i++){
		for(int j = 0; j <= i; j++){
			if(!j){
				c[i][j] = 1;//
			}
			else{
				c[i][j] = (c[i - 1][j] + c[i-1][j-1])%mod;
			}
		}
	}
}

int main(){
	
	init();
	int n;
	scanf("%d",&n);
	while(n--){
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",c[a][b]);
	}
	return 0;
}

组合数Ⅱ

C a b = a ! b ! ∗ ( a − b ) ! C_a ^ b = \frac{a!}{b! * (a-b)!} Cab=b!(ab)!a!
除法需要通过求逆元的方式,即 f a c t ( a ) ∗ i n f a c t ( b ) ∗ i n f a c t ( ( a − b ) ) fact(a) * infact(b) * infact((a-b)) fact(a)infact(b)infact((ab))

#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;
const int N = 1e5 + 10, mod = 1e9 + 7;

int fact[N];//阶乘
int infact[N];//逆元
int qmi(int a, int k, int p){
	ll res = 1 % p;
	while(k){
		if(k&1) res = (1ll)* res * a % p;
		a = (1ll) * a * a % p;
		k >>= 1;
	}
	return res;
}
int main(){
	//预处理
	fact[0] = infact[0] = 1;
	for(int i = 1; i < N; i++){
		fact[i] = (1ll) * fact[i-1]*i%mod;
		infact[i] = (1ll)*infact[i - 1] * qmi(i,mod-2, mod) % mod;
	}
	int n;
	scanf("%d",&n);
	while(n--){
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",(1ll) * fact[a] * infact[b] % mod * infact[a- b]%mod);
	}
} 

组合数 Ⅲ

在这里插入图片描述

卢卡斯定理
C a b = C a   m o d   p b   m o d   p ∗ C a / p b / p   m o d   p C_a^b = C_{a \ mod \ p} ^ {b \ mod \ p} * C_{a/p} ^ {b/p} \ mod \ p Cab=Ca mod pb mod pCa/pb/p mod p
除法部分,通过快速幂求逆元 来解决
时间复杂度
T ∗ p ∗ l o g p N ∗ l o g 2 p T*p*log_p N * log_2 p TplogpNlog2p,T等于多少组(20),N为 1 < = b < = a < = 1 e 18 1<=b <=a <=1e^{18} 1<=b<=a<=1e18 ,p最大 1 e 5 1e^5 1e5
因当p等于 1 e 5 1e^5 1e5时, l o g p N log_p N logpN 等于3~4
20 ∗ 1 e 5 ∗ 20 约 等 于 4 e 7 20 * 1e^5 * 20 约等于 4e^7 201e5204e7

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;

int n;
int p;
int qmi(int a, int k){
	ll res = 1 % p;
	while(k){
		if(k&1) res = (1ll) * res * a % p;
		a = (1ll) * a * a % p;
		k >>= 1;
	}
	return res;
}
int C(int a, int b){
    if(b > a) return 0;
	int res = 1;
	for(int i = 1, j = a; i <= b; i++,j--){
		res = (1ll)*res * j % p;
		res = (1ll) * res * qmi(i,p-2) % p;
	}
	return res;
}
int lucas(ll a, ll b){
	if(a < p && b < p) return C(a,b);
	return (1ll)*C(a%p,b%p) * lucas(a/p,b/p)%p;
} 
int main(){
	scanf("%d",&n);
	while(n--){
		ll a,b;
		scanf("%lld%lld%d",&a,&b,&p);
		cout << lucas(a,b) << endl;
	}
	return 0;
}

组合数 Ⅳ

在这里插入图片描述

因限制了64M的内存,所以开C[5010][5010]的数组会爆内存

正确做法
1.筛素数(1~5000)
2.求每个质数的次数
3.用高精度乘法吧所有质因子乘起来
C a b = a ∗ ( a − 1 ) ∗ . . . ∗ ( a − b + 1 ) b ∗ ( b − 1 ) ∗ . . . ∗ 1 C_a^b = \frac{a*(a-1)*...*(a-b+1)}{b*(b-1)*...*1} Cab=b(b1)...1a(a1)...(ab+1)
= a ∗ ( a − 1 ) ∗ . . . ∗ ( a − b + 1 ) ∗ ( a − b ) ! b ∗ ( b − 1 ) ∗ . . . ∗ 1 ∗ ( a − b ) ! =\frac{a*(a-1)*...*(a-b+1) * (a - b)!}{b*(b-1)*...*1 * (a-b)!} =b(b1)...1(ab)!a(a1)...(ab+1)(ab)!
= a ! b ! ∗ ( a − b ) ! = \frac{a!}{b!*(a-b)!} =b!(ab)!a!
分解质因数后:
= p 1 a 1 ∗ p 2 a 2 ∗ . . . ∗ p k a k =p_1^{a_1}*p_2^{a_2}*...*p_k^{a_k} =p1a1p2a2...pkak
求分子分解 每个p的次数具体见大佬博客,%%%大佬
答案为: ∏ i P i 分 子 次 数 和 − 分 母 次 数 和 \prod_i P_i^{分子次数和-分母次数和} iPi
不是很理解,粗糙的走了个过场

#include <iostream>
#include <algorithm>
#include <vector> 

using namespace std;

const int N = 6e3 + 10; 
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;
		}
	}
}
int get(int n,int p){
	int res = 0;
	while(n){
		res += n/p;
		n /= p;
	}
	return res;
}
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;
	}
	while(t){
		c.push_back(t%10);
		t/=10;
	}
	return c;
}
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(a-b,p) - get(b,p);
	}
	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]);
	puts("");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值