题目如下:
描述
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
输入
第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。
输出
对输入的每组数据M和N,用一行输出相应的K。
解法一:
标准的递归解法,代码中的注释已经给出充分的解释:
#include <iostream>
using namespace std;
// 计算将m个相同的苹果放入n个相同的盘子中的不同分法数量
int countWays(int m, int n) {
// 基本情况:苹果数为0或者只有一个盘子时,只有一种分法
if (m == 0 || n == 1) {
return 1;
}
// 若苹果数小于盘子数,避免重复计算,调整为将苹果数和盘子数相等进行计算
if (m < n) {
return countWays(m, m);
} else {
// 递归求解总的分法数量,分为两种情况:
// 1. 将m-n个苹果放入n个盘子中
// 2. 将m个苹果放入n-1个盘子中
return countWays(m - n, n) + countWays(m, n - 1);
}
}
int main() {
int t;
cin >> t;
while (t--) {
int m, n;
cin >> m >> n;
// 调用countWays函数计算分法数量,并输出结果
cout << countWays(m, n) << endl;
}
return 0;
}
复杂度分析:
时间复杂度:
- 对于每个测试用例,递归函数
countWays
需要计算苹果分配的不同方式,最坏情况下需要遍历所有可能的分法。 - 如果考虑每种分法都进行了计算,则时间复杂度为指数级别,即O(2^min(m, n))。
空间复杂度:
- 递归调用会在堆栈中占用空间,随着递归深度的增加,空间复杂度也会增加。
- 由于递归需要保存每层的状态,因此空间复杂度为O(min(m, n))。
解法二:
本人的解法,看到这题让我回想起了高中时期的排列组合问题最经典的隔板法,但是又有些许不同:隔板法得到的结果是有序的,而本题要求的结果是无序的。所以考虑分苹果的时候保持前一个盘子放的苹果不小于后一个盘子放的苹果的个数用来排除掉隔板法导致的相同的结果。
写出状态转移方程:
设第i个盘装完后剩余j个苹果,且第i个盘装了k个苹果的所有情况总数为dp[i][j][k],那么:
对于第i+1个盘子,置入不大于k个(l个)苹果的情况,构建转移方程:
最后统计所有盘子都用完且不剩余苹果的情况。
代码如下:
#include<bits/stdc++.h>
using namespace std;
void solution(int n,int m){
vector<vector<vector<int>>> dp
= vector<vector<vector<int>>>
(n+1,vector<vector<int>>(m+1,vector<int>(m+1,0)));
//dp[n][m][k]为装完第n个盘子剩下m个果子且这个盘子装了k个果子共有的可能数
dp[0][m][m] = 1;//未将苹果置入盘子的情况
for(int i=0;i<n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=m;k++){
for(int l=0;l<=j && l<=k;l++){
dp[i+1][j-l][l] += dp[i][j][k];
}
}
}
}
int ret = 0;
for(int k=0;k<=m;k++){
ret += dp[n][0][k];
}
cout << ret << endl;
}
int main(){
int t;
cin >> t;
vector<int> m(t),n(t);
for(int i=0;i<t;i++){
cin >> m[i] >> n[i];
}
for(int i=0;i<t;i++){
solution(n[i],m[i]);
}
return 0;
}
复杂度分析:
时间复杂度:
- 在动态规划解法中,利用三维数组
dp
存储中间结果,采用循环遍历的方式填充数组,时间复杂度取决于循环次数。 - 外层循环
for(int i=0;i<n;i++)
执行n次,内部3
个嵌套循环的总体执行次数受m限制,所以总体时间复杂度为O(n * m^2)。
空间复杂度:
- 三维数组
dp
的大小为(n+1) * (m+1) * (m+1)
,用来存储中间结果,因此空间复杂度为O(n * m^2),随着输入规模的增加而线性增长
感谢你能看到这里。