C++算法组合数の四大应用场景 彩色图文分布详解&定理应用证明【附例题&AC代码】

组合数的计算公式:c(a,b) = ( a*(a-1)* … (a-b+1) ) / (b(b-1)* … * 1) = a! / ( b! * (a-b)! )

常用的组合数的递推式:c(a,b)=c(a-1,b)+c(a-1,b-1)

给出一个实际的应用场景来解释这个递推式。从a个苹果中选出b个,所有情况都可以分为两大类(选中一个苹果标记为红色,作为 划分的标准)。
第一类:选的当中包含红色的【说明还要从剩余的(a-1)个苹果里选出(b-1)个】;
第二类:选的当中不包含红色的【说明还要从剩余的(a-1)个苹果里选出b个】;

例题

在这里插入图片描述

思路
n为1e5,a、b为 2x1e3,2x1e8直接暴力会TLE
2000^2000=4*1e6 所以先预处理出所有情况的方案数,每次只需要做一次查询就行了
时间复杂度:O(N^2)

// AC代码
#pragma GCC optimize(3 , "Ofast" , "inline")
#include <bits/stdc++.h>
using namespace std;
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)  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 ;
}

难度提升1:查询的次数不变,每次查询的数据变大
时间复杂度:O(N*logN)

当 a,b扩大到1e5时,需要运用 逆元快速幂预处理所有的阶乘

逆元阶乘可以直接相乘,也就是说a的逆元乘上b的逆元就是a*b的逆元【证明:如果x是a的逆元,y是b的逆元,那么ax=1,by=1,所以abxy 1,所以xy是ab的逆元】

// AC代码
#include <bits/stdc++.h>
#define ll long long 
using namespace std;
const int N=100010, 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 %p;
		a = (ll)a*a %p;
		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;
		infact[i] = (ll)infact[i-1] * qmi(i, mod-2, mod) %mod;
	}
	
	int n;
	scanf("%lld",&n);
	while(n--)
	{
    
    
		int a,b;
		scanf("%lld %lld",&a,&b);
		
		//三个阶乘相乘会溢出ll,所以要及时模 
		printf("%d\n",(ll)fact[a] * infact[b] %mod * infact[a-b] %mod); 
    }
	return 0;
} 

难度提升2:查询的次数很少,但是每次查询的数据暴大
在这里插入图片描述

需要用到 卢卡斯定理(Lucas):c(a,b) ≡ c(a mod p , b mod p) * c(a/p , b/p) [≡ 表示在mod p 的情况下同余 ]

Lucas的证明不需要掌握,会用上面的定理即可!!!【注意:当a和b都小于p时,可以直接从定义出发去做】

下面搞一蛤 Lucas 的简单(LJ)证明
先将a和b分别变成一个类似p进制的东西
在这里插入图片描述

接着用生成函数的方法可以证明
在这里插入图片描述

因为p是质数,不包含任何小于p的质因子,所以中间这些项模上p的余数都为0 (因为分母里面有p,分子里面一定没有p)
在这里插入图片描述

// AC代码
#include <bits/stdc++.h>
#define ll long long 
using namespace std;

int p;

int qmi(int a,int k)
{
    
    
	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 res = 1;
	for(int i=1,j=a; i<=b; i++,j--)
	{
    
    
	// 乘j,除以i  等于  乘j,乘i的逆元
		res = (ll)res*j%p;
		res = (ll)res*qmi(i,p-2)%p;
	}
	return res;
}

int lucas(ll a,ll b)
{
    
    
	if(a<p&&b<p)  return C(a,b); //当 a和 b都小于 p 时,从定义出发来算的那种情况
	return  (ll)C(a%p,b%p)*lucas(a/p,b/p)%p;
}

int main()
{
    
    
	int n;
	cin>>n;

	while(n--)
	{
    
    
		ll a,b;
		cin>>a>>b>>p;
		cout<<lucas(a,b)<<endl;
	}
	return 0;
}

难度提升3:组合数不能取模,结果会很大,需要用大数运算(高精度乘和高精度除)

在这里插入图片描述

直接根据公式用高精度就的话,时间效率会比较低,而且也比较难写。

一般的处理方式
在这里插入图片描述

第一步:筛素数,1~5000内的素数筛出来。 

第二步:求每个质数的一个次数, 利用a!=...这个公式来(能够保证不重不漏)
【 不重不漏的解释:假设一个数是p^k的倍数,那这个数里有k个p,那么一定会恰好被+k次
 (因为算p的倍数的时候+了一次,算p^2的倍数的时候+了一次...算p^k的倍数的时候+了一次) 】

第三步:用高精度乘法把所有的质因子乘到一块儿去。

// AC代码
#include <bits/stdc++.h>
#include <vector>
#define ll long long 
using namespace std;

const int N=5010;

int primes[N],cnt;
int sum[N]; //存每一个质数的次数 
bool st[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) //求n的阶乘里包含的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; //表示进位,最开始的时候初始化为0 
	for(int i=0; i<a.size(); i++)
	{
    
    
		t += a[i]*b; //a[i]表示当前这一位
		c.push_back(t%10);
		t /= 10; 
	}
	
	while(t)
	{
    
    
		c.push_back(t%10);
		t /= 10;
	}
	
	return c;
}

int main()
{
    
    
	int a,b;
	cin >> 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); //求一下当前这个数里包含的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;
}

猜你喜欢

转载自blog.csdn.net/Luoxiaobaia/article/details/108809291