1. 程式人生 > >Lucas定理及組合數取模

Lucas定理及組合數取模

引入 楊輝三角 std 數據 組合數取模 有關 ans main include

引入:

組合數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定理及組合數取模