1. 程式人生 > 其它 >P2822 [NOIP2016 提高組] 組合數問題

P2822 [NOIP2016 提高組] 組合數問題

題目傳送門

零、理解與感悟

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;
    }
}