学习笔记:组合数

在这里插入图片描述

不同情境下组合数的求法

1、求组合数 I(直接预处理答案,用数组存)
给定n组询问,每组询问给定两个整数a,b,请你输出C(a,b) mod (1e9+7)的值。

输入格式
第一行包含整数n。
接下来n行,每行包含一组a和b。

输出格式
共n行,每行输出一个询问的解。

数据范围
1≤n≤10000, 1≤b≤a≤2000


做法:

有一万次询问,如果每次都算一遍,那么肯定会超时,再来看数据范围a和b都不大于2000,可以提前把所有的C(a,b)算出来,用数组存起来,这样每次询问的时候直接输出就好了

const int N = 2010, 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==0)	c[i][j]=1;
			else	c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}

int main()
{
    
    
	int t;	cin>>t;
	init();
	
	while(t--)
	{
    
    
		int a,b;
		cin>>a>>b;
		cout<<c[a][b]<<endl;
	}
	return 0;
}

2、求组合数 II(预处理阶乘,用数组存,预处理答案数组存不下)
给定n组询问,每组询问给定两个整数a,b,请你输出Cba mod (1e9+7)的值。

输入格式
第一行包含整数n。

接下来n行,每行包含一组a和b。

输出格式
共n行,每行输出一个询问的解。

扫描二维码关注公众号,回复: 12485427 查看本文章

数据范围
1≤n≤10000, 1≤b≤a≤1e5


做法:
与上一题的思路类似,也是先提前预处理出来,但是数组存不下这么大的范围,我们可以根据组合数原本的公式:c(n,m) = n! / ( (n-m)! m! ) ,把所有要用到的阶乘预处理出来,注意因为要求的是模上1e9+7的结果,并且计算式中有除法,这里不仅要算出所有的阶乘,也要算出所有阶乘的逆元,因为模数1e9+7是质数,直接用费马小定理算逆元即可

const int N = 1e5+5, mod = 1e9+7;

int fact[N],infact[N]; //阶乘 及 其逆元 

int power(int a,int b,int p)
{
    
    
	int res=1;
	while(b)	{
    
     if(b&1) res=(ll)res*a%p; a=(ll)a*a%p; b>>=1; }
	return res;
}

void init()
{
    
    
	fact[0]=infact[0]=1;
	for(int i=1;i<N;i++)	fact[i]=(ll)fact[i-1]*i%mod;
	for(int i=0;i<N;i++)	infact[i]=power(fact[i],mod-2,mod);
}

int C(int n,int m)
{
    
    
	int res=(ll)fact[n]*infact[n-m]%mod*infact[m]%mod; //根据定义式计算
	return res;
}

int main()
{
    
    
	int t;	cin>>t;
	init();
	
	while(t--)
	{
    
    
		int a,b;
		cin>>a>>b;
		cout<<C(a,b)<<endl;
	}
	return 0;
}

3、求组合数 III(求非常大的数的阶乘,Lucas定理)
在这里插入图片描述

给定n组询问,每组询问给定三个整数a,b,p,其中p是质数,请你输出C(a,b)mod p的值。

输入格式
第一行包含整数n。
接下来n行,每行包含一组a,b,p。

输出格式
共n行,每行输出一个询问的解。

数据范围
1≤n≤20, 1≤b≤a≤1e18, 1≤p≤1e5

int p;

int power(int a,int b)
{
    
    
	int res=1;
	while(b)	{
    
     if(b&1) res=(ll)res*a%p; a=(ll)a*a%p; b>>=1; }
	return res;
}

int C(int n,int m)
{
    
    
	int res=1;
	for(int i=1;i<=m;i++)	res=(ll)res*(n-m+i)%p*power(i,p-2)%p; //除i变成乘i的逆元
	return res;
}

int lucas(ll a,ll b)
{
    
    
	if(a<p&&b<p)	return C(a,b);
	return (ll)C(a%p,b%p)*lucas(a/p,b/p)%p;
}

void solve()
{
    
    
	ll a,b;
	cin>>a>>b>>p;
	cout<<lucas(a,b)<<endl;
}

int main()
{
    
    
	int t; cin>>t;
	while(t--)	solve();
	return 0;
}

4、求组合数 IV(用高精度求一个组合数,质数处理法)
输入a,b,求C(a,b)的值。
注意结果可能很大,需要使用高精度计算。

输入格式
共一行,包含两个整数a和b。

输出格式
共一行,输出C(a,b)的值。

数据范围
1≤b≤a≤5000


思路:
根据定义式:
但是要做一些处理,因为又要乘又要除不仅很麻烦,直接挨个乘除的效率也非常慢,所以可以把C(n,m) 提前分解成几个质数相乘的形式(任何一一个大于1的整数都能分解成有限个质数相乘的形式)
C(n,m) = P1a1P2a2……Pkak,这样只需要写一个高精度乘法就好了,并且调用该乘法函数的次数相对一开始也少了很多,效率很高
所以要先把质数筛选出来,求n!中质因子p的个数也是一个经典的算法,然后用高精度乘法一个一个乘就好了

const int N = 5010;

int cnt[N];
vector<int> prime;
bool vis[N];

void get_prime(int n) //线性筛 
{
    
    
	for(int i=2;i<=n;i++)
	{
    
    
		if(!vis[i])	prime.push_back(i);
		for(int j=0;prime[j]<=n/i;j++)
		{
    
    
			vis[prime[j]*i]=1;
			if(i%prime[j]==0)	break;
		}
	}
}

int get(int n,int p) //得到n!中质因子p的个数 
{
    
    
	int cnt=0;
	while(n)	{
    
     cnt+=n/p; n/=p; }
	return cnt;
}

vector<int> mul(vector<int> &A,int b) //高精 乘 低精 
{
    
    
	vector<int> C;
	for(int i=0,t=0;i<A.size()||t;i++)
	{
    
    
		if(i<A.size())	t+=A[i]*b;
		C.push_back(t%10);
		t/=10;
	}
	return C;
}

int main()
{
    
    
	int a,b;
	cin>>a>>b;
	
	get_prime(a); //筛选质数 
	
	for(int i=0;i<prime.size();i++)
		cnt[i]=get(a,prime[i])-get(a-b,prime[i])-get(b,prime[i]);
	
	vector<int> res(1,1);
	for(int i=0;i<prime.size();i++)
		for(int j=0;j<cnt[i];j++)
			res=mul(res,prime[i]);
	
	for(int i=res.size()-1;~i;i--)	cout<<res[i];	puts("");
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_50815157/article/details/113820539