聯賽前數學知識
PART ONE 質數
一、質數的判定
1、定義
若一個正整數無法被除了 \(1\) 和它自身之外的任何自然數整除,則稱該數為質數(或素數),否則稱該正整數為合數。
在整個自然數集合中,質數的數量不多,分佈比較稀疏,對於一個足夠大的整數 \(N\),不超過 \(N\) 的質數大約有 \(\frac{N}{lnN}\) 個,即每 \(lnN\) 個數中大約有 \(1\) 個質數。
2、一些性質
\((1)\)、兩個質數一定是互質數。例如,\(2\)與\(7\)、\(13\)與\(19\)。
\((2)\)、一個質數如果不能整除另一個合數,這兩個數為互質數。例如,\(3\)與\(10\)、\(5\)
\((3)\)、\(1\)不是質數也不是合數,它和任何一個自然數在一起都是互質數。如\(1\)和\(9908\)。
\((4)\)、相鄰的兩個自然數是互質數。如\(15\)與\(16\)。
\((5)\)、相鄰的兩個奇數是互質數。如\(49\)與\(51\)。
\((6)\)、\(2\)和任意奇數互質
3、試除法
掃描\(2\sim \sqrt{n}\)之間的所有整數,依次檢查它們能否整除\(N\),若都不能整除,則\(N\)是質數,否則\(N\)是合數。
程式碼
bool is_prime(int n){ if(n<2) return 0; int m=sqrt(n); for(int i=2;i<=m;i++){ if(n%i==0) return 0; } return 1; }
二、質數的篩法
1、Eratosthenes篩法
\(Eratosthenes\) 篩法基於這樣的想法:任意整數 \(x\) 的倍數\(2x\),\(3x\),…都不是質數。根據質數的定義,上述命題顯然成立。
我們可以從\(2\)開始,由小到大掃描每個數\(x\),把它的倍數\(2x\),\(3x\),…,\([\frac{N}{x}] \times x\)標記為合數。
當掃描到一個數時,若它尚未被標記,則它不能被\(2 \sim x-1\)之間的任何數整除,該數就是質數。
時間複雜度\(O(NloglogN)\)
程式碼
const int maxn=1e6+5; bool vis[maxn]; void get_prime(int n){ for(int i=2;i<=n;i++){ if(vis[i]) continue; printf("%d\n",i); int m=n/i; for(int j=i;j<=m;j++){ vis[i*j]=1; } } }
2、線性篩
埃氏篩中,每個合數會被多次標記
而線上性篩中,每個合數只會被它的最小質因子篩一次,因此時間複雜度為\(O(N)\)。
程式碼
const int maxn=1e6+5;
int pri[maxn];
bool not_prime[maxn];
void xxs(int n){
not_prime[0]=not_prime[1]=1;
for(int i=2;i<=n;++i){
if(!not_prime[i]){
pri[++pri[0]]=i;
}
for (int j=1;j<=pri[0] && i*pri[j]<=n;j++){
not_prime[i*pri[j]]=1;
if(i%pri[j]== 0) break;
}
}
}
三、質因數分解
1、算術基本定理
任何一個大於\(1\)的正整數都能唯一分解為有限個質數的乘積,可寫作:
\[N=p_1^{c_1} \times p_2^{c_2}…p_m^{c_m} \]
其中\(c_i\)都是正整數,\(p_i\)都是質數,且滿足\(p_1<p_2<…<p_m\)
2、試除法
結合質數判定的“試除法”和質數篩選的“\(Eratosthenes\)篩法”,我們可以掃描\(2\sim\sqrt{n}\)的每個數\(d\),若\(d\)能整除\(N\),則從\(N\)中除掉所有的因子\(d\),同時累計除去的\(d\)的個數。
因為一個合數的因子一定在掃描到這個合數之前就從\(N\)中被除掉了,所以在上述過程中能整除\(N\)的一定是質數。
最終就得到了質因數分解的結果,易知時間複雜度為\(O(\sqrt{n})\)。
特別地,若\(N\)沒有被任何\(2\sim\sqrt{n}\)的數整除,則\(N \)是質數,無需分解。
程式碼
const int maxn=1e6+5;
int cnt[maxn],pri[maxn];
void div(int n){
int m=sqrt(n);
for(int i=2;i<=m;i++){
if(n%i==0){
pri[++pri[0]]=i,cnt[pri[0]]=0;
while(n%i==0){
n/=i;
cnt[pri[0]]++;
}
}
}
if(n>1){
pri[++pri[0]]=n;
cnt[pri[0]]=1;
}
for(int i=1;i<=pri[0];i++){
printf("%d %d\n",pri[i],cnt[i]);
}
}
PART TWO 約數
一、定義
若整數\(n\)除以整數\(d\)的餘數為\(0\),即\(d\)能整除\(n\),則稱\(d\)是\(n\)的約數,\(n\)是\(d\)的倍數,記為\(d|n\)。
二、算術基本定理的推論
在算術基本定理中,若正整數\(N\)被唯一分解為
\[N=p_1^{c_1} \times p_2^{c_2}…p_m^{c_m} \]
其中\(c_i\)都是正整數,\(p_i\)都是質數,且滿足\(p_1<p_2...<p_m\),
則N的正約數集合可寫作:
\[\{p_1^{b_1} p_2^{b_2}...p_m^{b_m}\} \]
其中\(0 \leq b_i \leq c_i\)
\(N\)的正約數的個數為
\[(c_1+1) \times (c_2+1) \times ... \times (c_m+1)=\prod_{i=1}^m(c_i+1) \]
\(N\)的所有正約數的和為
\[(1+p_1^1+p_1^2+...p_1^{c_1})\times...\times(1+p_m^1+p_m^2+...p_m^{c_m})=\prod_{i=1}^m(\sum_{j=0}^{c_i}(p_i)^j) \]
三、求N的正約數集合:試除法
掃描\(d=1\sim\sqrt{N}\),嘗試\(d\)能否整除\(N\),若能整除,則\(\frac{N}{d}\)也是\(N\)的約數。時間複雜度為\(O(\sqrt{N})\)
程式碼過水,就不放了
試除法的推論
一個整數\(N\)的約數個數上界為\(2\sqrt{n}\)
四、求1~N每個數的正約數集合——倍數法
const int maxn=1e6+5;
std::vector<int> g[maxn];
void div(int n){
for(int i=1;i<=n;i++){
int m=n/i;
for(int j=1;j<=m;j++){
g[i*j].push_back(i);
}
}
for(int i=1;i<=n;i++){
for(int j=0;j<g[i].size();j++){
printf("%d ",g[i][j]);
}
printf("\n");
}
}
倍數法推論
\(1\sim N\)每個數的約數個數的總和大約為\(NlogN\)。
五、最大公約數
定義:一組數的公約數,是指同時是這組數中每一個數的約數的數。而最大公約數,則是指所有公約數裡面最
大的一個,常縮寫為 \(gcd(Greatest\ Common\ Divisor)\)。
1、輾轉相除法(歐幾里得法)求兩個數的最大公因數
證明
任意\(a\),\(b\)屬於\(N+\),\(a \geq b\),有\(gcd(a,b)=gcd(b,a-b)=gcd(a,a-b)\)。
任意\(a\),\(b\)屬於\(N+\),有\(gcd(2a,2b)=2gcd(a,b)\)。
根據最大公約數的定義,後者顯然成立,我們主要證明前者。
對於\(a\),\(b\)的任意公約數d,因為\(d|a,d|b\),所以\(d|(a-b)\)。因此\(d\)也是\(b,a-b\)的公約數,反之亦成立。
故\(a,b\)的公約數集合與\(b,a-b\)的公約數集合相同。於是它們的最大公約數自然也相等,對於\(a,a-b\)同理。
程式碼
int getgcd(int aa,int bb){
if(bb==0) return aa;
return getgcd(bb,aa%bb);
}
時間複雜度\(O(log(a+b))\)
2、輾轉相減法(尼考曼徹斯法)求兩個數的最大公因數
證明
若\(a<b\),則\(gcd(b,a mod b)=gcd(b,a)=gcd(a,b)\),命題成立。
若\(a>b\),不妨設\(a=q \times b+r\),其中\(0 \leq r<b\)。顯然\(r=a mod b\)。
對於\(a,b\)的任意公約數\(d\),因為\(d|a,d|q \times b\),故\(d|(a-q \times b)\),即\(d|r\),因此\(d\)也是\(b\),\(r\)的公約數。反之亦成立。
故\(a,b\)的公約數集合與\(b,a modb\)的公約數集合相同。於是它們的最大公約數自然也相等。
程式碼
int getgcd(int aa,int bb){
return aa==bb ? aa:getgcd(aa>bb ? aa-bb:aa, bb>aa ? bb-aa:bb);
}
複雜度基本保持在\(O(logn)\),不過有概率退化成\(O(n)\)。
如果寫高精度的話,用這一種會比較方便。
3、求兩個數的最小公倍數
\(lcm(aa,bb)=aa\times bb \div gcd(aa,bb)\)
4、求多個數的最大公因數或最小公倍數
兩兩相求就可以了
5、擴充套件歐幾里得定理
定義
對於不完全為 \(0\) 的整數 \(a\),\(b\),\(gcd(a,b)\)表示 \(a\),\(b\) 的最大公約數。那麼一定存在整數 \(x\),\(y\) 使得 \(gcd(a,b)=ax+by\)。
求法
int exgcd(int aa,int bb,int &x,int &y){
if(bb==0){
x=1,y=0;
return aa;
}
int ans=exgccd(bb,aa%bb,x,y);
int t=x;
x=y;
y=t-aa/bb*y;
return ans;
}
證明
設\(ax+by=t\),當\(b=0\)時,\(t=a\),顯然有\(x=1,y=0\)
設\(ax_1+by_1=gcd(a,b),bx_2+(a\%b)y_2=gcd(b,a\%b)\)
由於\(gcd(a,b)=gcd(b,a%b)\),
聯立有:\(ax_1+by_1=bx_2+(a\%b)y_2\)
\(ax_1+by_1=bx_2+(a-a/b \times b)y_2\)
將\(a\),\(b\)示為未知數:整理等式
\(ax_1+by_1=ay_2+b(x_2-y_2(a/b))\)
\(x_1=y_2, y_1=x_2-(a/b)*y_2\)
一般解
六、線性篩約數個數與約數和
1、線性篩約數個數
const int maxn=1e6+5;
bool not_pri[maxn];
int pri[maxn],f[maxn],n,p;
void xxs(){
not_pri[0]=not_pri[1]=1;
for(int i=1;i<=n;i++){
if(i==1)f[i]=1;
if(!not_pri[i]){
pri[++pri[0]]=i;
f[i]=2;
}
for(int j=1;i*pri[j]<=n && j<=pri[0];j++){
not_pri[i*pri[j]]=1;
if(i%pri[j]==0){
int now=i*pri[j],cnt=0;
while(now%pri[j]==0){
now/=pri[j];
cnt++;
}
f[i*pri[j]]=f[now]*(cnt+1);
break;
}
f[i*pri[j]]=f[i]*f[pri[j]];
}
}
}
2、線性篩約數和
\(f[i]\) 表示\(i\)的約數和
\(e[i]\) 表示\(i\)的約數中,不能被\(i\)的最小素因子整除的約數和
const int maxn=1e6+5;
bool not_pri[maxn];
int pri[maxn],n;
long long f[maxn],e[maxn];
void xxs(){
not_pri[0]=not_pri[1]=1;
for(int i=1;i<=n;i++){
if(i==1){
f[i]=1,e[i]=0;
}
if(!not_pri[i]){
pri[++pri[0]]=i;
f[i]=i+1;
e[i]=1;
}
//預處理1和質數的情況
for(int j=1;i*pri[j]<=n && j<=pri[0];j++){
not_pri[i*pri[j]]=1;
if(i%pri[j]==0){
f[i*pri[j]]=f[i]*pri[j]+e[i];
//要減去i中是pri[j]的倍數的那些約數
e[i*pri[j]]=e[i];
break;
}
f[i*pri[j]]=f[i]*(pri[j]+1);
//將i擴大pri[j]倍後,i和i*pri[j]的約數互不相同,因此直接相加
e[i*pri[j]]=f[i];
//此時原先i的約數都和pri[j]互質
}
}
}
七、互質與尤拉函式
1、定義:
任意\(a\),\(b\)屬於\(N+\),若\(gcd(a,b)=1\),則稱\(a\),\(b\)互質。
對於三個數或更多個數的情況,我們把\(gcd(a,b,c)=1\)的情況稱為\(a\),\(b\),\(c\)互質;
把\(gcd(a,b)=gcd(a,c)=gcd(b,c)=1\)稱為\(a\),\(b\),\(c\)兩兩互質。後者顯然是一個更強的條件。
2、尤拉函式
定義
\(1\sim N\)中與\(N\)互質的數的個數被稱為尤拉函式,記為\(φ(N)\)。
若在算術基本定理中,
\(N=p_1^{c_1} \times p_2^{c_2}...p_m^{c_m}\)
則:
\(φ(N)=N \times \frac{p_1-1}{p_1} \times \frac{p_2-1}{p_2} \times ... \times \frac{p_m-1}{p_m}=N \times \prod_{p|N} (1-\frac{1}{p})\)(其中\(p\)為質數)
證明:
設\(p\)是\(N\)的質因子,\(1 \sim N\)中\(p\)的倍數有\(p\),\(2p\),\(3p\)…,\((N/p) \times p\),共\(N/p\)個。
同理,若\(q\)也是\(N\)的質因子,則\(1 \sim N\)中\(q\)的倍數有\(N/q\)個。
如果我們把這\(N/p+N/q\)個數去掉,那麼\(p \times q\)的倍數被排除了兩次,需要加回來一次。
因此,\(1 \sim N\)中不與\(N\)含有共同質因子\(p\)或\(q\)的數的個數為:
\(N-\frac{N}{p}-\frac{N}{q}+\frac{N}{p \times q}=N\times (1-\frac{1}{p}-\frac{1}{q}+\frac{1}{p\times q})=N \times (1-\frac{1}{p}) \times (1-\frac{1}{q})\)
類似地,可以在\(N\)的全部質因子上使用容斥原理,即可得到\(1 \sim N\)中不與\(N\)含有任何共同質因子的數的個數,也就是與\(N\)互質的數的個數。
根據尤拉函式的計算式,我們只需要分解質因數,即可順便求出尤拉函式。
3、尤拉函式求法
單個尤拉函式值
int getphi(int xx){
int ans=xx;
int m=(int)sqrt(xx+0.5);
for(int i=2;i<=m;i++){
if(xx%i==0){
ans=ans/i*(i-1);
while(xx%i==0) xx/=i;
}
}
if(xx>1) ans=ans/xx*(xx-1);
return ans;
}
時間複雜度\(O(\sqrt{n})\)
線性篩求1~n的尤拉函式值
void getphi(int n){
phi[1]=1;
for(int i=1;i<=n;i++){
if(!isnot_prime[i]){
prime[++prime[0]]=i;
phi[i]=i-1;
}
for(int j=1;j<=prime[0] && i*prime[j]<=n;j++){
isnot_prime[i*prime[j]]=1;
if(i%prime[j]==0){
phi[i*prime[j]]=phi[i]*prime[j];
//將i*pri[j]分成pri[j]段,每一段都有phi[i]個數與其互質
break;
} else {
phi[i*prime[j]]=phi[i]*(prime[j]-1);
//根據積性函式性質
}
}
}
}
時間複雜度\(O(n)\)
積性函式
如果 \(a\),\(b\) 互質,並且有 \(f(ab)=f(a) \times f(b)\),那麼就說函式為積性函式。
PART THREE 同餘
一、同餘類與剩餘系
1、同餘
定義
若整數\(a\),\(b\)除以正整數\(m\)的餘數相等,則稱 \(a\),\(b\) 模 \(m\)同餘,記為 \(a≡b(modm)\)
2、同餘類 & 剩餘系
定義
對於任意正整數 \(a(0\leq a \leq m-1)\)
集合 \(\{a+km\}(k=0,1,2,3...)\) 模 \(m\) 同餘,餘數為 \(a\) ,則稱該集合為一個模 \(m\) 的同餘類。
顯然,\(m\) 的同餘類一共有\(m\)個,它們構成 \(m\) 的完全剩餘系。
二、一些定理
1、費馬小定理
內容
若\(p\)為素數,對於任意整數 \(a\),有
\(a^{p-1}≡1(mod p)\)
也可以寫成 \(a^p≡ a(mod p)\)
2、尤拉定理
內容
若正整數 \(a\),$ n$ 互質,則有 \(a^{φ(n)}≡a(mod p)\),其中 \(φ(n)\) 是尤拉函式
擴充套件尤拉定理
3、二次探測定理
內容
若 \(p\) 為質數且\(x∈(0,p)\),則方程 \(x^2≡1(mod p)\) 的解為 \(x_1=1\),\(x_2=p-1\)。
4、裴蜀定理
內容
方程 \(ax+by=c\)(\(a\)為正整數,\(b\)為正整數)有解的充要條件是 \(gcd(a,b)|c\)。
注意其中 \(c\) 必須大於等於 \(gcd(a,b)\)。
三、乘法逆元
1、定義
若\((a \times x) ≡1(mod b)\)
則稱 \(x\) 為 \(a\)在模 \(b\)意義下的乘法逆元,記為 \(a^{-1}\)。
注意:並非所有的情況下都存在乘法逆元,但是當 \(gcd(a,b)=1\)即\(a,b\)互質時,存在乘法逆元
2、費馬小定理求逆元
3、 尤拉定理求逆元
4、 擴充套件歐幾里得求逆元
5、線性求逆元
6、比較
注意:求逆元往往涉及大量的乘法,所以運算的時候一定要注意是否需要用到 long long。
四、中國剩餘定理(CRT)
1、基本內容
中國剩餘定理主要用於求解模數互質的一組線性同餘方程的解。
設\(m_1,m_2,m_3,....,m_n\)為兩兩互質的整數
\(m=\prod _{i=1}^{n}m_i,M_i=m/m_i\)
\(t_i\)是線性同餘方程 \(M_it_i \equiv 1(mod\ m_i)\) 的一個解
對於任意的\(n\)個整數\(a_1,a_2,a_3,...,a_n\),方程組
\(\begin{cases} x\equiv a_1(mod\ m_1)\\x\equiv a_2(mod\ m_2)\\...\\x\equiv a_n(mod\ m_n)\end{cases}\)
有整數解,解為\(x=\sum _{i=1}^na_iM_it_i\)
2、證明
因為\(M_i=m/m_i\)是除\(m_i\)之外所有模數的倍數
所以\(\forall k \neq i,a_iM_it_i \equiv0(mod\ m_k)\)
又因為\(a_iM_it_i \equiv a _i (mod\ m_i)\)
所以代入\(x=\sum_{i=1}^na_iM_it_i\),原方程組成立
3、通解
\(x=x+k\times m\)
\(k\)為整數
4、程式碼
#include<cstdio>
const int maxn=25;
#define int long long
int ex_gcd(int aa,int bb,int &x,int &y){
if(bb==0){
x=1,y=1;
return aa;
}
int ans=ex_gcd(bb,aa%bb,x,y);
int t=x;
x=y;
y=t-aa/bb*y;
return ans;
}
int a[maxn],b[maxn],n,T=1,t[maxn],val[maxn];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&a[i],&b[i]);
T*=a[i];
}
for(int i=1;i<=n;i++){
int xx,yy;
t[i]=T/a[i];
ex_gcd(t[i],a[i],xx,yy);
val[i]=xx;
}
int ans=0;
for(int i=1;i<=n;i++){
ans=ans+b[i]*val[i]*t[i];
}
ans=(ans%T+T)%T;
printf("%lld\n",ans);
return 0;
}
PART FOUR 高斯消元
一、內容
高斯消元是一種求解線性方程組的方法
所謂線性方程組,是由\(M\)個\(N\)元一次方程共同構成的
線性方程組的所有係數可以寫成一個\(M\)行\(N\)列的係數矩陣
再加上每個方程等號右側的常數,可以寫成一個\(M\)行\(N+1\)列的增廣矩陣
求解這種方程組的步驟可以概括成對增廣矩陣的三類操作
\(1\)、用一個非零的數乘某一行
\(2\)、把其中一行的若干倍加到另一行上
\(3\)、交換兩行的位置
如果最後得到的矩陣中出現某一行的係數全為\(0\),但是常數不為\(0\),則說明方程無解
如果出現某一行的係數全部為\(0\),並且常數也為\(0\),則說明方程有無窮多解
二、程式碼
#include<cstdio>
#include<cmath>
#include<iostream>
typedef double db;
const int maxn=105;
const db eps=1e-20;
db mp[maxn][maxn],ans[maxn],mmax;
int n,jl,now=1;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n+1;j++){
scanf("%lf",&mp[i][j]);
}
}
for(int i=1;i<=n;i++){
mmax=0;
for(int j=now;j<=n;j++){
if(fabs(mp[j][i])>fabs(mmax)){
mmax=mp[j][i];
jl=j;
}
}
if(fabs(mmax)<eps) continue;
if(now!=jl) std::swap(mp[now],mp[jl]);
for(int j=i+1;j<=n+1;j++){
mp[now][j]/=mp[now][i];
}
mp[now][i]=1.0;
for(int j=now+1;j<=n;j++){
double cs=mp[j][i];
for(int k=i;k<=n+1;k++){
mp[j][k]-=mp[now][k]*cs;
}
}
now++;
}
bool pd=0;
for(int i=1;i<=n;i++){
int ncnt=0;
for(int j=1;j<=n;j++){
if(std::fabs(mp[i][j])<eps) ncnt++;
}
if(ncnt==n && std::fabs(mp[i][n+1]>eps)){
printf("-1\n");
return 0;
} else if(ncnt==n){
pd=1;
}
}
if(pd){
printf("No Solution\n");
return 0;
}
ans[n]=mp[n][n+1];
for(int i=n-1;i>=1;i--){
ans[i]=mp[i][n+1];
for(int j=i+1;j<=n;j++){
ans[i]-=(mp[i][j]*ans[j]);
}
}
for(int i=1;i<=n;i++){
printf("%.2lf\n",ans[i]);
}
return 0;
}
PART FIVE 組合數
一、 定義
從\(n\)個不同元素中,任取\(m(m≤n)\)個元素併成一組,叫做從\(n\)個不同元素中取出\(m\)個元素的一個組合;從\(n\)個不同元素中取出\(m(m≤n)\)個元素的所有組合的個數,叫做從\(n\)個不同元素中取出\(m\)個元素的組合數。
二、求法
1、 公式法
計算組合數的一般公式:
\(C^m_n=\frac{n!}{m!(n-m)!}\)
其中\(n!=1\times2\times\cdots\times nn\)
特別地,定義\(0!=1\)
如果模數為質數,我們就可以提前處理出階乘的逆元和逆元的階乘
#define int long long
int getC(int n,int m){
if(m==0) return 1ll;
return jc[n]%mod*jcc[n-m]%mod*jcc[m]%mod;
}
signed main(){
ny[1]=1;
for(int i=2;i<=n;i++){
ny[i]=(mod-mod/i)*ny[mod%i]%mod;
}
jc[0]=1;
for(int i=1;i<=n;i++){
jc[i]=jc[i-1]*i%mod;
}
jcc[0]=1;
for(int i=1;i<=n;i++){
jcc[i]=jcc[i-1]*ny[i]%mod;
}
int n,m;
scanf("%lld%lld",&n,&m);
printf("%lld\n",getC(n,m));
}
2、 遞推法
針對大多數僅僅是利用組合數求解問題的題目運用遞推法打表,不僅方便,而且可以穩穩地控制複雜度,對於需要多次引用組合數的題目效果極佳:
基於組合數公理性質:\(C^m_n=C^{n-m}_n\)
(請大家務必記住此公式,由此在考場上靈活使用)
推得:\(C^m_n=C^{m-1}_{n-1}+C^m_{n-1}\)
由這個遞推公式就可以熟練的寫出組合數程式碼,但要注意初始化:
\(C^0_0=0\)
\(C^i_0=C^1_0=C^1_1=1\)( \(i\)為自然數 )
同時,把表打出來後,我們會發現———這就是楊輝三角,這個三角可以解決很多問題,記住列印三角的方法也可以打出組合數。
c[0][0]=c[1][0]=c[1][1]=1;
for(int i=2;i<=2000;i++){
c[i][0]=1;
for(int j=1;j<=i;j++){
c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
三、盧卡斯定理
1、定理內容
普通的求組合數一般提前預處理出來階乘的逆元。
但是當資料範圍巨大的時候我們無法進行預處理,或者說給的\(mod\)很爛的時候,這時候我們就要用到盧卡斯定理來解決這個問題。
若\(p\)是質數,則對於任意整數\(1 \leq m \leq n\),有
\[C_n^m \equiv C_{n\ mod\ p}^{m\ mod\ p}\times C_{n/p}^{m/p}(mod\ p) \]
時間複雜度\(O(p+log_pn)\)
2、程式碼
#include<cstdio>
const int maxn=1e5+5;
int t,n,m,p,ny[maxn],jc[maxn],jcc[maxn];
int get_C(int nn,int mm){
if(nn<mm) return 0;
return 1LL*jc[nn]*jcc[mm]%p*jcc[nn-mm]%p;
}
int lks(int aa,int bb){
if(bb==0) return 1;
return 1LL*lks(aa/p,bb/p)*get_C(aa%p,bb%p)%p;
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d%d",&n,&m,&p);
ny[1]=1;
for(int i=2;i<=p;i++){
ny[i]=1LL*(p-p/i)*ny[p%i]%p;
}
jcc[0]=jc[0]=1;
for(int i=1;i<=p;i++){
jc[i]=1LL*jc[i-1]*i%p;
jcc[i]=1LL*jcc[i-1]*ny[i]%p;
}
int ans=lks(n+m,n);
printf("%d\n",ans);
}
return 0;
}
四、卡特蘭數
1、基本模型
有一個長度為 \(2n\)的\(01\)序列,其中\(1,0\)各 \(n\)個,要求對於任意的整數 $k \in [1,2n] $,數列的前 \(k\)個數中,\(1\)的個數不少於\(0\)
滿足條件的序列的數量為
\[Cat_n=\frac{C_{2n}^n}{n+1} \]
另一種形式
\[Cat_n=C_{2n}^n-C_{cn}^{n-1} \]
遞推式
\[Cat_n=\sum_{i=0}^{n-1}Cat_i \times Cat_{n-i-1} \]
2、證明
3、推論
以下問題都與卡特蘭數有關
1、\(n\)個左括號和\(n\)個右括號組成的合法括號序列的數量為\(Cat_n\)
2、\(1,2,...,n\)經過一個棧,形成的合法出棧序列的數量為\(Cat_n\)
3、\(n\)個節點構成的不同二叉樹的數量為\(Cat_n\)
4、在平面直角座標系上,每一步只能向上或向右走,從\((0,0)\)走到\((n,n)\)並且除兩個端點外不接觸直線\(y=x\)的路線數量為\(2Cat_n-1\)