概念:
組合數我們用C(n,m)表示,它代表在n個數中取m個數的方案。(這個概念主要用於將問題抽象到組合數上)。
公式:
組合數的公式也不多,
1、C(n,m)=C(n,n-m)。
2、C(n,m)=C(n-1,m-1)+C(n-1,m)。這個很重要,因爲這個和楊輝三角的遞推公式一樣的,所以我們經常把楊輝三角和組合數和起來看。典題
3、C(0,n)+C(1,n)+C(2,n)+C(3,n)+…C(n,n)=2 ^ n,這個公式被我們成爲二次項定理,這個也經常用。
這上面三個就是我們經常用的(省選大佬出門左拐)。
求法:
對於組合數的求法挺多的:Lucas定理、遞推、逆元。
遞推:
這個其實就是在推楊輝三角,這個主要是用於n和m<=2000並且要用到很多的時候用的。舉個栗子
對於這種問題我們就比較適合遞推。
適用範圍:
n<=2000(n是組合數的下標的值,即C(n,m)中的n)。
板子代碼:
#include<iostream>
#include<cstdio>
using namespace std;
int C[1000][1000],mod=1e9+9;
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=0;i<=n;i++)//邊界一定要全
{
C[i][1]=i%mod;//對於i個數中取一個數方案是i
C[i][i]=1;//在i個數中取i個數只有一種方案
}
for(int i=2;i<=n;i++)//直接開始遞推
for(int j=2;j<i;j++)//邊界不要計算
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;//根據第二個公式,也就是楊輝三角的遞推式
printf("%d",C[n][m]);//輸出答案
return 0;
}
題目代碼:
#include<iostream>
#include<cstdio>
using namespace std;
long long C[2010][2010],sum[2010][2010],mod;
void pre()
{
for(int i=0;i<=2000;i++)
{
C[i][1]=i%mod;
C[i][i]=1;
}
for(int i=2;i<=2000;i++)
for(int j=2;j<i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
for(int i=1;i<=2000;i++)
for(int j=1;j<=i;j++)//預處理出在i個數中1到j中有多少個組合數是mod的倍數
if(C[i][j]==0)//因爲對mod取餘,所以等於0就是mod的倍數
sum[i][j]=sum[i][j-1]+1;//前綴和計算
else sum[i][j]=sum[i][j-1];//不是的話就直接等於
return ;
}
int main()
{
int n,m,T;
scanf("%d %d",&T,&mod);//多組測試數據
pre();//直接求出2000以內所有組合數
while(T--)
{
long long ans=0;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
if(i>m)//這個要注意,因爲如果i<m的話min(i,m)是等於i的
ans+=sum[i][m];
else ans+=sum[i][i];
printf("%lld\n",ans);//輸出答案
}
return 0;
}
階乘逆元:
這個方法就是根據組合數的定義公式去求,根據C(n,m)=n!/(m!*(n-m)!),所以我們要預處理出所有的階乘以及逆元。首先你要會階乘逆元的預處理。不會階乘逆元的點這
適用範圍:
n<mod(n的意義上同,因爲如果n>mod的話,其中有的階乘就會因爲是mod的倍數而沒有逆元,這樣就錯了)
板子代碼(費馬小定理):
#include<iostream>
#include<cstdio>
using namespace std;
long long inv[1000100],jc[1000100],mod=1e9+9;
long long pow(long long a,long long b)//快速冪
{
long long ans=1;
while(b)
{
if(b%2==1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b=b>>1;
}
return ans;
}
void pre()
{
jc[1]=1;//階乘邊界
for(int i=2;i<=1000000;i++)
jc[i]=(jc[i-1]*i)%mod;//求階乘
inv[1000000]=pow(jc[1000000],mod-2);//用費馬小定理求
for(int i=999999;i>=0;i--)
inv[i]=(inv[i+1]*(i+1))%mod;//倒推求階乘逆元
return ;
}
int main()
{
pre();
int T;
scanf("%d",&T);
while(T--)
{
long long n,m;
scanf("%lld %lld",&n,&m);
printf("%lld\n",(((jc[n]*inv[m])%mod)*inv[n-m])%mod);//求答案,記得mod,一直mod,乘完就mod!
}
}
板子代碼(拓展歐幾里得):
#include<iostream>
#include<cstdio>
using namespace std;
int mod=1e9+9,inv[1000100],jc[1000100],x,y;
void gcd(int a,int b)//拓展歐幾里得
{
if(b==0)
{
x=1;
y=0;
return ;
}
gcd(b,a%b);
int k=x;
x=y;
y=k-a/b*y;
return ;
}
void pre()
{
jc[1]=1;
for(int i=2;i<=1000000;i++)//求階乘
jc[i]=(jc[i-1]*i)%mod;
gcd(jc[1000000],mod);//求最大階乘逆元
inv[1000000]=(x+mod)%mod;//賦值
for(int i=999999;i>=0;i--)//倒推階乘逆元
inv[i]=(inv[i+1]*(i+1))%mod;
return ;
}
int C(int n,int m)
{
return (((1LL*jc[n]*inv[m])%mod)*inv[n-m])%mod;//組合數
}
int main()
{
int T;
scanf("%d",&T);
pre();
while(T--)
{
int n,m;
scanf("%d %d",&n,&m);//讀入
printf("%d\n",C(n,m));//輸出答案
}
return 0;
}
Lucas定理:
這個定理個人覺得還好,主要是針對mod很小,如果用階乘的話就有可能會是mod的倍數,這樣逆元求組合數就gg了。可是對於遞推又被卡的情況下我們就只能用這個了。
適用範圍:
在遞推和階乘逆元都掛的時候,就用這個。
公式:
Lucas(n,m,mod)(n和m是C(n,m)中的,mod就是取模對象)=C(n%mod,m%mod) * Lucas(n/mod,m/mod,mod)。
代碼:
這個代碼有兩種,因爲我們可以看見,在公式中我們是有組合數的,只不過範圍降了下來,所以我們主要是用Lucas定理把原本卡死遞推和階乘逆元給救回來。所以我們纔有兩種代碼。當mod比較小時我們可以採用遞推,而mod比較大時我們可以用階乘逆元。
遞推:
#include<iostream>
#include<cstdio>
using namespace std;
int C[2010][2010],mod;
void pre()//預處理
{
for(int i=1;i<=mod;i++)
{
C[i][1]=i%mod;
C[i][i]=1;
}
for(int i=2;i<=mod;i++)
for(int j=2;j<i;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
return ;
}
int Lucas(int n,int m)
{
if(m==0)//邊界
return 1;
return (C[n%mod][m%mod]*Lucas(n/mod,m/mod))%mod;//Lucas定理的公式
}
int main()
{
int T;
scanf("%d %d",&T,&mod);
pre();
while(T--)
{
int n,m;
scanf("%d %d",&n,&m);
printf("%d\n",Lucas(n,m));//Lucas定理
}
return 0;
}
階乘逆元:
對於這個雖然有了小優化,可還是有一個條件:mod必須是質數纔可以,要不然題目保證mod以內的階乘不會是mod的倍數。
費馬小定理:
#include<iostream>
#include<cstdio>
using namespace std;
int jc[1000100],inv[1000100],mod;
int pow(long long a,long long b)//快速冪
{
long long ans=1;
while(b)
{
if(b%2==1)
ans=(ans*a)%mod;
a=(a*a)%mod;
b=b>>1;
}
return (int)ans;
}
void pre()//預處理階乘逆元
{
jc[1]=1;
for(int i=2;i<mod;i++)
jc[i]=(jc[i-1]*i)%mod;
inv[mod-1]=pow(jc[mod-1],mod-2);//費馬小定理
for(int i=mod-2;i>=0;i--)
inv[i]=(inv[i+1]*(i+1))%mod;
}
int C(int n,int m)//組合數公式
{
return (((jc[n]*inv[m])%mod)*inv[n-m])%mod;
}
int Lucas(int n,int m)
{
if(m==0)//邊界
return 1;
return (C(n%mod,m%mod)*Lucas(n/mod,m/mod))%mod;//Lucas定理公式
}
int main()
{
int T;
scanf("%d %d",&T,&mod);
pre();
while(T--)
{
int n,m;
scanf("%d %d",&n,&m);
printf("%d\n",Lucas(n,m));//輸出
}
return 0;
}
拓展歐幾里得:
#include<iostream>
#include<cstdio>
using namespace std;
int mod,inv[1000100],jc[1000100],x,y;
void gcd(int a,int b)//拓展歐幾里得
{
if(b==0)
{
x=1;
y=0;
return ;
}
gcd(b,a%b);
int k=x;
x=y;
y=k-a/b*y;
return ;
}
void pre()//預處理
{
jc[1]=1;
for(int i=2;i<mod;i++)
jc[i]=(jc[i-1]*i)%mod;
gcd(jc[mod-1],mod);//處理出解
inv[mod-1]=(x+mod)%mod;//把解取正
for(int i=mod-2;i>=0;i--)
inv[i]=(inv[i+1]*(i+1))%mod;
return ;
}
int C(int n,int m)//組合數嘗龜
{
return (((1LL*jc[n]*inv[m])%mod)*inv[n-m])%mod;
}
int Lucas(int n,int m)
{
if(m==0)
return 1;
return (1LL*C(n%mod,m%mod)*Lucas(n/mod,m/mod))%mod;
}
int main()
{
int T;
scanf("%d %d",&T,&mod);//多組測試數據,mod的值
pre();
while(T--)
{
int n,m;
scanf("%d %d",&n,&m);
printf("%d\n",Lucas(n,m));//輸出
}
return 0;
}
以上就是組合數提高組的所有知識,希望大家看後可以有理解。
如果有不清楚的歡迎留言詢問。