P2822 [NOIP2016 提高組] 組合數問題
阿新 • • 發佈:2021-08-23
零、理解與感悟
1、通過楊輝三角形象記憶帕斯卡公式(程式碼實現的遞推式)
2、二維字首和優化
這裡需要注意的是要根據題意,抽象中一箇中間態的a陣列,模擬二維字首和的前置原始陣列。這個陣列的獲取有一點點說道,因為所有C陣列資料初始值是0,而我們要根據是0,才會把a陣列對應位置存入1,這時,就可能把範圍外的0也錯誤的記錄到a陣列中。
所以,下面的程式碼第一層迴圈表示是從N個數中取,第二層表示取i個最多,這樣才不會超界,第二層迴圈千萬不要無腦的寫成j=1;j<N;j++,這樣就不是組合數的範圍了,那樣的C[i][j]=0,也不知道是原始的0,還是算完的0了。
for (int i = 1; i < N; i++) for (int j = 1; j < i; j++) if (!C[i][j])a[i][j] = 1;//如果組合數陣列的數字為0,表示此處有一個0
一、帕斯卡公式(楊輝三角)+暴力計算
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 2010; //n的數值上限 int t; //t次查詢 int n; //n個數字中 int m; //找m個數字組合 int k; //給定k,求k的倍數 int C[N][N]; //組合數陣列 int main() { cin >> t >> k; //預處理楊輝三角形 for (int i = 0; i < N; i++) { //base case C[i][0] = C[i][i] = 1; //組合數C(n,0)=1 組合數C(n,n)=c(n,0)=1 //遞推生成其它組合數 for (int j = 1; j < i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % k; } //處理t次詢問 while (t--) { int ans = 0; cin >> n >> m; //這是最樸素的方法,但每次計算,效能差,因為詢問次數多,每次都現從頭計算, //不是離線計算,是線上計算,不適合多次詢問這一方式,可以考慮採用某一種方法進行離線計算就好了。 //這時就需要引入二維字首和,否則有兩個點TLE,只會得90分。 for (int i = 1; i <= n; i++) for (int j = 1; j <= min(i, m); j++) if (!C[i][j]) ans++; cout << ans << endl; } }
二、帕斯卡公式+二維字首和優化
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N = 2010; //n的數值上限 int t; //t次查詢 int n; //n個數字中 int m; //找m個數字組合 int k; //給定k,求k的倍數 int C[N][N]; //組合數陣列 int a[N][N]; //因為組合數C陣列,內容並不是1的個數,需要一個轉換,這個轉換就是a陣列。 int s[N][N]; //二維字首和 int main() { cin >> t >> k; //預處理楊輝三角形 for (int i = 0; i < N; i++) { //base case C[i][0] = C[i][i] = 1; //組合數C(n,0)=1 組合數C(n,n)=c(n,0)=1 //遞推生成其它組合數 //因為是求組合數C(A,B),那麼A一定要大於等於B,否則就不對了。 //所以,j的迴圈範圍最大就只能是i for (int j = 1; j < i; j++)//因為一頭一尾j-->0和i都被手動設定了1,所以迴圈的範圍就是中間剩下的了。 C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % k;//因為C陣列中已有的資料都是被%k後存入的, // 所以這裡做加法前不用再%,再%也是那麼回事,反而速度更慢了。 } //生成模擬原始陣列 //因為是求組合數C(A,B),那麼A一定要大於等於B,否則就不對了。 //所以,j的迴圈範圍最大就只能是i,其它的範圍,還是預設值0,但那個預設值0可不是%k後造成的值為0,所以,這裡的生成模擬原始陣列時, //一定要注意迴圈的範圍,判斷在範圍內的值為0的才是一個解,範圍外的和我沒關係。 for (int i = 1; i < N; i++) for (int j = 1; j < i; j++) if (!C[i][j])a[i][j] = 1;//如果組合數陣列的數字為0,表示此處有一個0 //標準的二維字首和 for (int i = 1; i < N; i++) for (int j = 1; j < N; j++) s[i][j] = a[i][j] + s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1]; //處理t次詢問 while (t--) { cin >> n >> m; cout << s[n][m] << endl; } }