Lucas定理及組合數取模
引入:
組合數C(m,n)表示在m個不同的元素中取出n個元素(不要求有序),產生的方案數。定義式:C(m,n)=m!/(n!*(m-n)!)(並不會使用LaTex QAQ)。
根據題目中對組合數的需要,有不同的計算方法。
(1)在模k的意義下求出C(i,j)(1≤j≤i≤n)共n2 (數量級)個組合數:
運用一個數學上的組合恒等式(OI中稱之為楊輝三角):C(m,n)=C(m-1,n-1)+C(m-1,n)。
證明:
1.直接將組合數化為定義式暴力通分再合並。過程略。
2.運用組合數的含義:設m個元素中存在一個“特殊”元素a,對從m個元素中選出n個元素進行分類討論。
第一種情況:n個元素中含有元素a,則只需在剩余m-1個元素中選出n-1個元素即可。方案數為C(m-1,n-1)。
第二種情況:n個元素中不含元素a,則只需在剩余m-1個元素中選出n個元素即可。方案數為C(m-1,n)。
這樣我們就得到了一個與組合數有關的遞推式,初始化C(i,0)=1,隨後通過遞推以O(n2)的復雜度完成計算。均攤O(1)。
例題:NOIP2016 D2T1 組合數問題 題目鏈接
題意:給定一個數k,然後給出t組m,n,對於每一組數據,詢問對於C(i,j)(0≤i≤n,0≤j≤min(i,m)),有多少個C(i,j)是k的倍數。
數據範圍:k≤21,m,n≤2000,t≤10000。子任務見題目鏈接。
題解:
70分做法:O(20002)預處理出所有組合數,然後每次暴力掃描C(i,j)判斷是否是k的倍數。然後機智地忘記取模(沒錯就是我233)
90分做法:在原有70分做法的預處理中加上取模,暴力掃描判斷是否為0。
100分做法:發現每次只是數據範圍改變,k和組合數都沒有改變,所以嘗試優化重復操作。
預處理+取模後,問題變為在整張組合數表中某個範圍內0的個數。我們將非0數置0,將0置1,問題轉化為矩陣和。用前綴和預處理可以做到O(1)查詢。
代碼:(將近一年前寫的 巨醜)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2000+10;
int c[maxn][maxn],d[maxn][maxn],s[maxn][maxn];
int t,n,m,k;
int main()
{
int i,j;
cin>>t>>k;
for(i=0;i<maxn;i++){c[i][0]=c[i][i]=1;}
for(i=1;i<maxn;i++){for(j=1;j<i;j++){c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;}}
for(i=0;i<maxn;i++){for(j=i+1;j<maxn;j++){c[i][j]=1;}}
for(i=0;i<maxn;i++){for(j=0;j<maxn;j++){if(c[i][j]){d[i][j]=0;}else{d[i][j]=1;}}}
for(i=0;i<maxn;i++){s[i][0]=s[i-1][0]+d[i][0];s[0][i]=s[0][i-1]+d[0][i];}
for(i=1;i<maxn;i++){for(j=1;j<maxn;j++){s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+d[i][j];}}
while(t--)
{
int ans=0;
scanf("%d%d",&n,&m);
//for(i=0;i<=n;i++){for(j=0;j<=min(i,m);j++){if(!c[i][j]){ans++;}}}
//cout<<ans<<endl;
printf("%d\n",s[n][m]);
}
return 0;
}
Lucas定理及組合數取模