组合数学(一本通)

发布时间 2023-05-27 17:22:49作者: shirlybabyyy

1648:【例 1】「NOIP2011」计算系数

 第一种方法:直接用杨辉三角求出二项式系数

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=1005; 
typedef long long LL;
int c[maxn][maxn];
int n,m,a,b,k;
int mod=10007;
int main(){
	cin>>a>>b>>k>>n>>m;
	for(int i=1;i<=k;i++){  //利用杨辉三角求出二项式系数 
		c[i][0]=c[i][i]=1;
		for(int j=1;j<=i-1;j++)  c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
	}
	int ans=c[k][m];
	a%=mod;b%=mod;
	int aa=1,bb=1;
	for(int i=1;i<=n;i++) aa=aa*a%mod;
	for(int j=1;j<=m;j++) bb=bb*b%mod;
	ans=ans*aa%mod*bb%mod;
	cout<<ans<<endl;
    return 0;
}

 第二种方法:用逆元破解除法取模

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e6+10;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//可以用杨辉三角求出当a=1,b=1时xnym的系数,其实就是C(k,n)即C(n+m,n)    如果有a,b的话,很显然就是把C(n+m,n)乘上anbm

//n+m=k  a,b<10^6
int mod=10007;
LL jc[maxn];
LL ksm(LL a,LL b){   //都记得要开LL  
	LL res=1;
	while(b){
		if(b&1){
			res=res*a%mod;
		}
		b>>=1;
		a=a*a%mod;
	}
	return res%mod;
}
//求组合数的算法
LL c(LL n,LL m){
	return jc[n]*(ksm(jc[m],mod-2)%mod)*(ksm(jc[n-m],mod-2)%mod);
} 
int main(){
	int a,b,k,n,m;
	jc[0]=jc[1]=1;
	scanf("%d %d %d %d %d",&a,&b,&k,&n,&m);
	for(int i=2;i<=k;i++){
		jc[i]=jc[i-1]*i%mod;
	}
	LL res=1;
	res=ksm(a,n)*ksm(b,m)%mod*c(n+m,n)%mod;
	printf("%lld\n",res);
return 0;
}

  

1649:【例 2】2^k 进制数

 其实根据样例分析一下也不难,主要是分类讨论,需要看最高位怎么处理,比如说样例里面,8进制的数字化为二进制至少不超过7位,那就说明了第七位只能取到1,也就是2^(w%k)-1,而数字从高位开始每一位又是递增的,所以要扣除小于首位的数字,那可以取得数字又少了。

2-len-1的第一类数:C(n,i)

len位的第二类数:for(int i=1;i<=c;i++)  C(n-i,m)       c=2^(w%k)-1

最后还要用高精度做组合数

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=282; 
const int INF=0x3fffffff;
int total[maxn];

int gcd(int a,int b){
	return b==0? a:gcd(b,a%b);
} 
void cc(int n,int m){ //求C(n,m) 
	if(n<m) return;
	int a[maxn],b[maxn]; //表示组合数的分子、分母
	for(int i=m;i>=1;i--){
		b[i]=i;   //分母  要被全部化掉 
		a[i]=n-m+i;   //分子 
	}
	for(int i=1;i<=m;i++){
		if(b[i]==1) continue; //约分  去掉分母
		for(int j=m;j>=1;j--){
			int x=gcd(b[i],a[j]);
			a[j]/=x;
			b[i]/=x;
			if(b[i]==1) break;
		} 
	}
	memset(b,0,sizeof(b)); //再拿来用
	b[1]=1;b[0]=1;
	//把约分后的分子相乘
	int jw=0;
	for(int j=1;j<=m;j++){
		jw=0;
		if(a[j]==1) continue; 
		for(int i=1;i<=b[0];i++){
			b[i]=b[i]*a[j]+jw;
			jw=b[i]/10;
			b[i]%=10;
			if(i==b[0]&&jw) b[0]++; //进位 
		}
	}
	//累加到total数组
	total[0]=max(total[0],b[0]);
	for(int i=1;i<=total[0];i++){
		total[i]+=b[i];
		total[i+1]+=total[i]/10;
		total[i]%=10;
	} 
	if(total[total[0]+1]) total[0]++;
}
int main(){
	int k,w,n,c,m;
	memset(total,0,sizeof(total)); 
	cin>>k>>w;
	n=(1<<k)-1;m=w/k;c=w%k;
	for(int i=m;i>=2;i--) cc(n,i);
	c=(1<<c)-1;
	if(c>=1&&n>m){  //还可以填剩下的位数,并且后面的数有的选,满足数组不降
		for(int i=1;i<=c;i++) cc(n-i,m); 
	}
	for(int j=total[0];j>=1;j--) cout<<total[j];
	return 0;
}

  

1650:【例 3】组合

 

 lucas定理模板题,记得开longlong

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
typedef long long ll; 
ll n,m,p;
ll ksm(ll a,ll b){
	ll s=1;
	while(b){
		if(b&1) s=s*a%p;
		a=a*a%p;
		b>>=1;
	}
	return s;
}
ll c(ll n,ll m){
	if(m>n) return 0;
	ll a=1,b=1;
	for(ll i=n-m+1;i<=n;i++) a=a*i%p;
	for(ll i=2;i<=m;i++) b=b*i%p;
	return a*ksm(b,p-2); //乘法逆元 
}
ll lucas(ll n,ll m){
	if(!m) return 1;
	else return c(n%p,m%p)*lucas(n/p,m/p)%p;
}
int main(){
	int t;
	cin>>t;
	while(t--){
		cin>>n>>m>>p;
		cout<<lucas(n,m)<<endl;
	}
	return 0;
}

  

1651:【例 4】古代猪文

这道题比较综合,会用到几个算法,首先题目不难推出结果式子 q^(sum(C(n,d)) mod q   其中d|n

首先q=999911659是个质数,q,n互质,可以由欧拉定理的推论推出 计算关键为 sum(C(n,d)) mod 9999116458 的结果

分解质因子:999911658=2*3*4679*35617,这样的数成为square-free-number ,所有质因子次数都为1

枚举n的约数d,用lucas定理计算出组合数,分别算出C(n,d)对2,3,4679,35617的模,记为a1,a2,a3,a4,然后用中国剩余定理求出线性同余方程组的解:

xmod 2=a1             xmod3=a2           xmod4679=a3     xmod35617=a4

(因为直接用lucas定理求组合数对999911658的模会超时)

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
typedef long long ll; 
const int a[4]={2,3,4679,35617};
int p[36000],b[4],mod=999911658,ans=0,n,g;
int i,j,x,y;
ll power(int a,int b){
	ll c=1;
	while(b){
		if(b&1) c=c*a%mod;
		a=(ll)a*a%mod;
		b>>=1;
	}
	return c;
}
void exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1;y=0;return;
	}
	exgcd(b,a%b,x,y);
	int z=x;
	x=y;
	y=z-(a/b)*y;
}
int inv(int a,int p){
	int x,y;
	exgcd(a,p,x,y);
	return (x%p+p)%p;
}
int calc(int x,int mod){
	int ans=1,y,a,b;
	for(y=n;x;x/=mod,y/=mod){
		a=x%mod;b=y%mod;   //lucas定理
		ans=(ll)ans*p[b]%mod*inv(p[a],mod)%mod*inv(b<a? 0:p[b-a],mod)%mod; //把递归转为了循环 
	}
	return ans; 
}
int main(){
	cin>>n>>g;
	g%=mod+1;
	if(g==0) {
		cout<<"0"<<endl;return 0;
	}
	
	for(p[0]=i=1;i<=a[3];i++) p[i]=(ll)p[i-1]*i%mod; //阶乘
	for(i=1;i*i<=n;i++){   //枚举所有的因子   从1开始 
		if(n%i==0){
			for(j=0;j<4;j++) b[j]=(b[j]+calc(i,a[j]))%a[j];
			if(i*i!=n)
			  for(j=0;j<4;j++) b[j]=(b[j]+calc(n/i,a[j]))%a[j]; //大的因子 
		}
	}
	for(i=0;i<4;i++) {
		//中国剩余定理
		exgcd(mod/a[i],a[i],x,y);
		ans=(ans+(ll)x*(mod/a[i])%mod*b[i])%mod;
	}
	ans=(ans+mod)%mod;
	mod++;
	ans=power(g,ans);
	cout<<ans<<endl;
	return 0;
}