学习NOTE 2——数论(之组合数学)

题外话

天杀的数论!!!

正题

下面进入正题。。。

是什么?

组合数学,其实就是排列组合(为什么组合有这么多内容,怨),还有两个重要的原理——“加法原理”和“乘法原理”。

加法原理

完成一件事有\(n\)种方法,\(a_i\)代表第\(i\)种方法的数目,那么就一共有:\(S=\Sigma_{i=1}^{n}a_i,(1\le i\le n)\)种不同的完成方法。

乘法原理

完成一件事要分\(n\)个步骤,\(a_i\)代表第\(i\)个步骤的不同方法数目。那么就一共有:\(S=\Pi_{i=1}^{n}a_i,(1\le i\le n)\)种不同的完成方法。

排列数 (A/P)

\(n\)个不同元素中,任取\(m\)\(m\le n\)\(m\)\(n\)均为自然数)个元素按照一定的顺序排成一列,
叫做从\(n\)个不同元素中取出\(m\)个元素的一个排列;从\(n\)个不同元素中取出\(m(m\le n)\)个元素的所有排
列的个数,叫做从\(n\)个不同元素中取\(m\)个元素的排列数,用符号\(A_{n}^{m}\)(或者是 \(P_{n}^{m}\))表示。

公式如下:

\[A_{n}^{m}=\frac{n!}{(n-m)!} \]

全排列其实就是\(A_{n}^{n}\),一种特殊情况。

组合数 (C)

组合数\(C_{n}^{k}\)表示从\(n\)个不同元素中,不考虑顺序地选取\(k\)个元素的方案数。

公式如下:

\[C_{n}^{k}=\frac{n!}{k!(n-k)!} \]

一些利用组合数性质的题:(都是洛谷的) P5520,P2181,P3414,P8692,P7157

怎么实现求组合数?

目前有\(3\)种(只是目前)。

1.杨辉三角

就是这个东东:


                 1
               1   1
             1   2   1
           1   3   3   1
         1   4   6   4   1
       1   5   10  10  5   1
       ......

原理:

它符合一个递推性质:

\[C_{n}^{k}=C_{n-1}^{k}+C_{n-1}^{k-1} \]

代码实现:

这是代码~
void C(){
	for(int i=0;i<=N;i++)
	for(int j=0;j<=i;j++){
		if(!j||j==i) c[i][j]=1;
		else c[i][j]=(c[i-1][j]+c[i-1][j-1])%M;
	}
}

\(\color{Red}一定要小心RE!!\)

例题:洛谷P2282

2.快速幂

\(n\)有点大的时候。

原理
计算组合数\(C_{n}^{k}=\frac{n!}{k!(n-k)!}\)涉及除法取模,可转化为乘法取模。
根据费马小定理,若\(p\)是质数且\(a\)\(p\)互质,那么\(a^{p-1}\equiv 1(\mod p)\)\(a\)关于\(p\)的逆元\(a^{-1}\equiv a^{p-2}(\mod p)\)

代码实现
先预处理\(1\)\(n\)的阶乘对\(p\)取模的值\(fact_n\)及其逆元\(ifact_n\),
再根据\(C_{n}^{k}=fact_n \times ifact_k \times ifact_{n-k} (\mod p)\)计算。

int qpow(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return res;
}
void init(){
	fact[0]=1;
	for(int i=1;i<=N;++i)
	fact[i]=fact[i-1]*i%MOD;
	ifact[N]=qpow(fact[MAXN],MOD-2);
	for(int i=N;i>0;--i)
	ifact[i-1]=ifact[i]*i%MOD;
}
/*
void init(){
	fact[0]=ifact[0]=1;
	for(int i=1;i<=N;++i){
		fact[i]=fact[i-1]*i%MOD;
		ifact[i]=ifact[i]*qpow(i,MOD-2)%MOD;
	}
}
*/
int C(int n,int k){
	if(k>n) return 0;
	return fact[n]*ifact[k]%MOD*ifact[n-k]%MOD;
}

例题:P1680

3.高精度+线性筛

用于\(n,m\)特别大的时候(\(10^{5}\)),这时就必须用高精度了!!

  1. 筛质数:把\(1-n\)之间的质数筛出来。
  2. 枚举所有质数
    • a. 计算\(C_{n}^{m}\)中质数\(p\) 的个数。
    • b. 利用高精度计算\(C_i \times\)

\(n!\)\(p\)的个数:\(s=\frac{n}{p}+\frac{n}{p^2}+\frac{n}{p^3}\dots\)

原理:
\(n\)非常大时,普通方法计算组合数可能溢出,需高精度计算。线性筛法可\(O(n)\)时间复杂度筛选质
数,先筛出一定范围内质数,再用高精度乘法和除法计算阶乘和逆元,进而计算组合数,计算中用数组
模拟高精度数运算。

代码实现:

int prim[N],vis[N],cnt;
void getprim(int n){ //筛素数
	for(int i=2;i<=n;i++){
		if(!vis[i]) prim[cnt++]=i;
		for(int j=0;i*prim[j]<=n;j++){
			vis[i*prim[j]]=1;
			if(i%prim[j]==0) break;
		}
	}
}
int get(int n,int p){//n!中p的个数
	int s=0;
	while(n) s+=n/p,n/=p;
	return s;
}
int getps(int n,int m,int p){//C中p的个数
	return get(n,p)-get(m,p)-get(n-m,p);
}
void multiply(int p,int &len){//高精度
	int t=0;
	for(int i=0;i<len;i++){
		t+=C[i]*p;
		C[i]=t%10;t/=10;
	}
	while(t) C[len++]=t%10;t/=10;
}
int main(){
	int n,m,C[N],len=1;C[0]=1;
	cin>>n>>m;
	getprim(n);
	for(int i=0;i<cnt;i++){
		int p=prim[i],s=getps(n,m,p);
		while(s--) multiply(p,len);
	}
	for(int i=len-1;i>=0;i--) cout<<C[i];
return 0;
}

\(持续更新中...\)

posted @ 2026-07-09 21:00  _T_M_T  阅读(3)  评论(0)    收藏  举报