1. 程式人生 > >劃分數、多重集組合數練習總結

劃分數、多重集組合數練習總結

劃分數練習總結


劃分數描述的就是有N種相同的東西,將他們劃分成M組,求有多少種不同的劃分(1,2,5 和 1,5,2 是一樣的),先來一段書上的話
這裡寫圖片描述
其中那個錯誤推導看得懂是啥子意思,但是後面那個正確推導 :
dp[i][j] = dp[i-1][j] + dp[i][j-i]是啥子情況喃?
其中 dp[i-1][j]就代表 j個物品,在分成i-1組中一共有好多情況(其中就包括只分成1組、只分成2兩組。。。), 那 dp[ i ] [ j - i ] 喃? 又表示啥子意思喃? 根據 dp[ i - 1] [ j] 的含義,我們現在要求的是 dp [ i ] [ j ] 也就是 j 個 物品分成 i 組有多少不同的情況, 我們現在已經曉得了分成 i - 1 組的所有情況,也就是說,只差必須分成 i 組的所有情況就得到答案了
dp[i][j-i]就代表把 j個物品必須分成 i組的情況,而要想 這i組,每組都分配的有東西,那起碼每組應該有一個東西在裡面佔位置, 所有就是 從j個物品中拿出 i個物品先切這 i個分組中先佔位, 所以就只剩了 j - i 個物品,然後這 j - i 個物品隨便放,因為不管這 j - i 個物品怎麼放, 肯定佔 i 個分組的。而且這 j - i 個物品有多少种放法,前面已經動態規劃出來了就是 dp[i][j-i]

#include<iostream>
#include<cstring>
using namespace std;
int length, row;
int shuzu[15][15];

int main(){
    int jishu;
    cin >> jishu;
    while(jishu --){
        cin >> length >> row;
        memset(shuzu, 0, sizeof(shuzu));
        for(int i = 0; i <= length; i++) shuzu[1
][i] = 1; for(int i = 2; i <= row; i++){ for(int j = 0; j <= length; j++){ if(j < i) { shuzu[i][j] = shuzu[i-1][j]; } else{ shuzu[i][j] = shuzu[i-1][j] + shuzu[i][j-i]; } } } cout
<< shuzu[row][length] << endl; } return 0; }

多重集組合數


說實話,不是炫耀,我剛剛學了劃分數過後,還沒有學多重集組合數的時候我就遇到了poj3046,我還是整出來了,雖然效率不咋樣,但是還是AC了,還是可以。
多重集組合數劃分數很像,劃分數說的是有n完全一樣的物品, 要將這些物品分裝到 小於等於m 個不同的盒子中,有多少種不同的情況,而多重集組合數 說的是有n種不一樣的物品,每個物品的數量若干,問將這些物品分裝到m個盒子中有多少種不同的情況
還是先上書上講的內容
這裡寫圖片描述
這裡寫圖片描述
我自己想的辦法就是這裡寫圖片描述
但是就跟書上說的一樣,這個效率還是太低,容易超時,所以就要研究這裡寫圖片描述
但是這個又咋個理解喃?
比如: 有3個物品,分別數量是 2 , 2 , 1, 然後將這些物品分成 3 組
根據dp [ i ] [ j ] 的含義,生成陣列 :
1 , 1 , 0
2 , 3 , 2
3 , 5 , 5
其實還是不清楚,再來分別解釋哈每個的含義,
我喜歡錶示成 dp[i][j] = dp[i-1][j] + dp[i][j-1] - dp[i][j-1-a]
其中dp[i-1][j] 求 i 行 j 列 的時候,上一行的同一列的元素,這個元素就代表還沒有把第 i 個元素考慮進來的時候, 前 i - 1 個元素在放入 j 個盒子中有多少種情況。
dp[i-1][j-1] + dp[i-1][j-2] + ... + dp[i-1][j-k] 這些東西就代表在考慮第 i 種物品的情況時, 有分別考慮第 i 種物品只有 1 個時、 只有 2 個時、只有 3個時。。。只有min(i的數量, 最大盒子數) 時,但是這樣一個一個加太浪費時間,而且我們之前已經把這些東西都加過一遍了,也就是 dp[i][j-1] 但是這個dp[ i ] [ j -1] 又有可能多加了一個東西,就是當 j-1-a >= 0 時,就會多加一個前面的dp[i-1][j-1-a] 就相當於平移造成的。 這相當於是在動態規劃的基礎上再利用動態規劃

#include<iostream>
#include<cstring>
using namespace std;
#define N 1000000
int length, cishu, S, R;
int input[1005] = {0};
int shuzu[1002][100000] = {0};

int main(){
    cin >> length >> cishu >> S >> R;
    int aa;
    for(int i = 0; i < cishu; i++){
        cin >> aa;
        input[aa] ++;
    }

    for(int i = 0; i <= length; i++) shuzu[i][0] = 1;
    for(int i = 1; i <= length; i++){
        for(int j = 1; j <= R; j++){
            if(j-1-input[i] >= 0){
                shuzu[i][j] = (N + shuzu[i-1][j] + shuzu[i][j-1] - shuzu[i-1][j-1-input[i]]) % N;
            }else{
                shuzu[i][j] = (shuzu[i-1][j] + shuzu[i][j-1]) % N;
            }
        }
    }

    int answer = 0;
    for(int i = S; i <= R; i++) 
    answer = (answer + shuzu[length][i]) % N;
    cout << answer << endl;
    return 0;
}


說實話這道題我方向都確定了,但是最後還是放棄了,看了答案過後還是有點遺憾。。。
還是轉化成多重集組合數,但是給出一個排列求是第幾個就不好整了,我的第一反應就是列舉出所有的排列(從10111100 轉化成 long long) 然後通過sort排序後,再用二分lower_bound() 方法來查詢,但是感覺不好整,要是通過在多重集組合數dp的同時,記錄所有的排列,情況太多了
這裡寫圖片描述
肯定超時。
然後我又想要不然用揹包dp來求,比如
輸入 : 7 , 4 , 3
然後通過多重揹包就可以求得有 1, 1, 2 , 3 和 1 , 2 , 2 , 2 這兩種組合,然後用next_permutation() 來獲得所有的排列,但是10個1,7個2,3個3就超時了
而且我想了個測試資料,輸入33 , 19 , 33
這裡寫圖片描述
這個答案太大了就說明就算能夠得出所有的排列,也會超時因為
for(int i = 0 ; i < 471435600; i++) {} 就這一句話都要超時,就不要說其他的了,所以基本可以斷定,這頂不是列舉出所有排列,而是通過給出的排列直接得結果,但是又咋個整喃? 未必這些排列之間存在某種規律 ? 可以通過函式來表達排列?反正後頭我就放棄了,直接看答案了。。。
其實,是通過上面多重集組合數動態規劃的結果,來求的,比如說要求3121這個數是第幾個,就是求3121前面有幾個數,求3121前面有幾個數,就是
先找第一位是1 和 2的所有排列
1 * * *
2 * * *
第一位是3的(也就是3 * * *)就不能現在直接照過來,因為要看後面幾位
然後就是找第2位,因為這個排列原本是10101交替,所以奇數位就是數值越大就越大,但是偶數就是數值越大越小了,又因為前面把1 、2 開頭的排列都找了,所以只剩3開頭的3 3 * *
3 2 * *
然後3 1 * * 又不能慌到直接拿過來,就去找第三位,按照上面的規律
3 1 1 *
然後3 1 2 * 也是不能現在找出來
3 1 2 3
3 1 2 2
然後就完了。。。

關鍵就是咋個切在上面的多重組合數dp結果中找我們想要的東西
比如輸入是 7 , 4 , 3,dp結果是
1 , 1 , 1 , 0 , 0 , 0 , 0
0 , 1 , 2 , 3 , 2 , 1 , 0
0 , 0 , 1 , 3 , 6 , 7 , 6
0 , 0 , 0 , 1 , 4 ,10 ,16

#include<iostream>
#include<cstring>
using namespace std;
int n, k, m;
int shuzu[35][35] = {0};
int jishu;
char input[40];

int getIndex(){
    int arr[40], length = 0;
    int qian = 1;
    for(int i = 1; input[i]; i++){
        if(input[i] != input[i-1]){
            arr[length++] = qian;
            qian = 0;
        }
        qian ++;
    }
    if(qian != 0) arr[length++] = qian;

    int answer = 0;
    int temp = n;
    for(int i = 0; i < length; i++){
        if(i%2 == 0){
            for(int j = 1; j < arr[i]; j++){
                if(temp > j)answer += shuzu[k-i-1][temp-j];
            }
        }else{
            for(int j = m; j > arr[i]; j--){
                if(temp > j)answer += shuzu[k-i-1][temp-j];
            }
        }
        temp -= arr[i];
    }   
    return answer;
}

int main(){
    cin >> n >> k >> m;
    shuzu[0][0] = 1;
    for(int i = 1; i <= k; i++){
        for(int j = 1; j <= n; j++){
            if(j-m-1 >= 0){
                shuzu[i][j] = shuzu[i-1][j-1] + shuzu[i][j-1] - shuzu[i-1][j-m-1];
            }else{
                shuzu[i][j] = shuzu[i-1][j-1] + shuzu[i][j-1];
            }
        }
    }   
    cout << shuzu[k][n] << endl;

    cin >> jishu;
    for(int i = 0; i < jishu; i++){
        cin >> input;
        int answer = getIndex();
        cout << answer << endl;
    }
    return 0;
}