劃分數、多重集組合數練習總結
劃分數練習總結
劃分數描述的就是有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;
}