学习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}^{n}\),一种特殊情况。
组合数 (C)
组合数\(C_{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
......
原理:
它符合一个递推性质:
代码实现:
这是代码~
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-n\)之间的质数筛出来。
- 枚举所有质数:
- 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;
}
\(持续更新中...\)

浙公网安备 33010602011771号