算法競賽中的小球放盒子問題

背景:寫題的時候遇到過很多關於這類問題的變種題,所以打算總結一下

問題分類

根據球是否不同,盒子是否不同,盒子是否為空,一共可以分為 \(2^{3}\) 種情況討論

Problem 1

題意

給定 N 個不同的球,放進 M 個不同的盒子,盒子允許為空,有多少種方案?

解析

從每個球的角度出發,每個球都有M种放法,所以就是 \(M^{N}\)

題目鏈接

Problem 1

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int qmi(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n,m;
	while(cin >> n >> m)
	cout << qmi(m,n) << endl;
	return 0;
}

Problem 2

題意

給定 N 個相同的球,放進 M 個不同的盒子,盒子不允許為空,有多少種方案?

解析

這個就是經典的隔板法問題,相當於把N個球用隔板分成M個部分
N個球有N-1個空隙,在空隙中選擇M-1個作為隔板將原來分成M個部分,所以答案是 \(C_{N-1}^{M-1}\)

題目鏈接

Problem 2

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int sp[20];
int qmi(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}
int C(int a,int b){
    if(b > a) return 0;
    int p = sp[a];
    int q = sp[b];
    int r = sp[a - b];
    int res = p / q;
    return res / r;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m;
    sp[0] = 1;
    for(int i = 1; i <= 15; i++) sp[i] = sp[i-1] * i;
    while(cin >> n >> m){
        cout << C(n-1,m-1) << endl;
    }
    return 0;
}

Problem 3

題意

給定 N 個相同的球,放進 M 個不同的盒子,盒子允許為空,有多少種方案?

解析

和問題2類似,只需要把隔板法變形一下。
我們可以假定每個隔板的右邊自帶了一個小球,那麼這樣總共就會有 N+M 個小球,選 M-1 個空隙,答案就是 \(C_{M+N-1}^{M-1}\)
每次插入板子後形成的組合,把右邊的球去掉,可以發現就是我們所需要的答案

題目鏈接

Problem 3

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int sp[20];
int qmi(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}
int C(int a,int b){
    if(b > a) return 0;
    else return sp[a] / sp[b] / sp[a-b];
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m;
    sp[0] = 1;
    
    for(int i = 1; i <= 20; i++) sp[i] = sp[i-1] * i;
    while(cin >> n >> m){
            cout << C(n+m-1,n) << endl;

    }
    return 0;
}

Problem 4

題意

給定 N 個相同的球,放進 M 個相同的盒子,盒子允許為空,有多少種方案?

解析

利用動態規劃解決,設 \(dp[i][j]\) 表示將i個小球放到j個盒子里的方案數,那麼初始化顯然有 \(dp[0][0] = dp[0][1] = …… = dp[0][m] = 1\)
轉移分兩種情況考慮,第一種就是允許有盒子為空的情況 那麼就是 \(dp[i][j-1]\)
第二種是不允許有空盒, 此時可以假定j個盒子里每個盒子已經有了一個球(或者可以理解為把每個盒子抽一個球之後再放回去)
這種方案數等價於 \(dp[i-j][j]\)
所以轉移方程就是 \(dp[i][j] = dp[i][j-1] + dp[i-j][j]\)

題目鏈接

Problem 4

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int sp[20];
int dp[20][20];
int qmi(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}
int C(int a,int b){
    if(b > a) return 0;
    else return sp[a] / sp[b] / sp[a-b];
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m;
    sp[0] = 1;
    for(int i = 1; i <= 20; i++) sp[i] = sp[i-1] * i;
    for(int i = 1; i <= 15; i++) dp[0][i] = 1;
    for(int i = 1; i <= 15; i++){
        for(int  j = 1; j <= 15; j++){
            if(i >= j)
                dp[i][j] = dp[i][j-1] + dp[i-j][j];
            else dp[i][j] = dp[i][j-1];
        }
    }
    while(cin >> n >> m){
            cout << dp[n][m] << endl;
    }
    return 0;
}

Problem 5

題意

給定 N 個相同的球,放進 M 個相同的盒子,盒子不允許為空,有多少種方案?

解析

跟第4個問題幾乎一樣,轉移方程也一樣,只不過這次變成了盒子不能為空
那麼最後的答案就是 \(dp[n-m][m]\) (即假定每個盒子已經有一個球了)

題目鏈接

Problem 5

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int sp[20];
int dp[20][20];
int qmi(int a,int b){
	int res = 1;
	while(b){
		if(b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}
int C(int a,int b){
    if(b > a) return 0;
    else return sp[a] / sp[b] / sp[a-b];
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m;
    sp[0] = 1;
    for(int i = 1; i <= 20; i++) sp[i] = sp[i-1] * i;
    for(int i = 1; i <= 15; i++) dp[0][i] = 1;
    for(int i = 1; i <= 15; i++){
        for(int  j = 1; j <= 15; j++){
            if(i >= j)
                dp[i][j] = dp[i][j-1] + dp[i-j][j];
            else dp[i][j] = dp[i][j-1];
        }
    }
    while(cin >> n >> m){
        if(n >= m)
            cout << dp[n-m][m] << endl;
        else cout << 0 << endl;
    }
    return 0;
}

Problem 6

題意

給定 N 個不同的球,放進 M 個相同的盒子,盒子不允許為空,有多少種方案?

解析

依然採用動態規劃解決,設 \(dp[i][j]\) 表示將i個小球放到j個盒子里的方案數
考慮兩種情況,第一種情況,多了一個空盒子,多出來的球就放這個空盒子里,即 \(dp[i-1][j-1]\)
第二種情況,多出來的球放到之前的盒子里,由於有j個相同的盒子,即 \(dp[i-1][j] \times j\)
所以轉移方程就是從 \(dp[i][j] = dp[i-1][j-1] + dp[i-1][j] \times j\)
其實這個是第二類斯特林數,存在通項公式可以優化時間複雜度,這裡不再贅述,有興趣的可以自己下來研究一下

題目鏈接

Problem 6

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int dp[20][20];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m;
    dp[0][0] = 1;
    for(int i = 1; i <= 15; i++){
        for(int j = 1; j <= 15; j++){
            dp[i][j] = dp[i-1][j-1] + j * dp[i-1][j];
        }
    }
    while(cin >> n >> m){
        cout << dp[n][m] << endl;
    }   
    return 0;
}

Problem 7

題意

給定 N 個不同的球,放進 M 個相同的盒子,盒子允許為空,有多少種方案?

解析

跟第6個問題一樣,答案抽象出來其實是 \(\sum_{i=1}^ndp[n][i]\)
因為這裡的區別就是有盒子可以為空,那麼在第6個問題中 \(dp[n][j]\) 表示成把n個球放進j個盒子里的方案數,並且盒子不為空
其實在這個問題中就等價於有j個盒子不為空,而另外m-j個盒子為空的情況

題目鏈接

Problem 7

代碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int dp[20][20];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m;
    dp[0][0] = 1;
    for(int i = 1; i <= 15; i++){
        for(int j = 1; j <= 15; j++){
            dp[i][j] = dp[i-1][j-1] + j * dp[i-1][j];
        }
    }
    while(cin >> n >> m){
        int res = 0;
        for(int i = 1; i <= m; i++){
            res += dp[n][i];
        }
        cout << res << endl;
    }   
    return 0;
}

Problem 8

題意

給定 N 個不同的球,放進 M 個不同的盒子,盒子不允許為空,有多少種方案?

解析

可以先假定盒子都相同,問題轉化為問題6
然後考慮不同盒子的排列即可
最後答案是 \(M! \times dp[n][m]\)

題目鏈接

Problem 8

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
int dp[20][20];
int sp[20];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n,m;
    sp[0] = 1;
    for(int i = 1; i <= 15; i++) sp[i] = sp[i-1] * i;
    dp[0][0] = 1;
    for(int i = 1; i <= 15; i++){
        for(int j = 1; j <= 15; j++){
            dp[i][j] = dp[i-1][j-1] + j * dp[i-1][j];
        }
    }
    while(cin >> n >> m){
        cout << sp[m] * dp[n][m] << endl;
    }   
    return 0;
}