我们需要将 m 个相同的苹果放入 n 个相同的盘子中,允许有的盘子空着不放。求解有多少种不同的分法。
输入两个整数 m,n(0<=m<=10; 1<=n<=10) 代表苹果数、盘子数。
输出一个整数,代表不同的分法数量。
输入:
3 2
输出:
2
说明:
在这个样例中,有 (0,3)、(1,2) 这两种不同的分法。
输入:
7 3
输出:
8
说明:
注意,由于苹果和盘子都是相同的,所以 (5,1,1) 和 (1,5,1) 是同一种分法。
这道题的核心是整数分拆问题和动态规划。主要涉及:
graph TD
A[状态定义] --> B[dp[i][j]]
B --> C[i个苹果放入j个盘子的方案数]
D[状态转移] --> E{i与j的关系}
E -->|i < j| F[dp[i][j] = dp[i][i]]
E -->|i >= j| G[两种情况相加]
G --> H[有空盘子: dp[i][j-1]]
G --> I[无空盘子: dp[i-j][j]]
F --> J[最终状态]
H --> J
I --> J
flowchart TD
A[递推分析] --> B{苹果数与盘子数}
B -->|m < n| C[必有空盘子]
B -->|m >= n| D[分两种情况]
C --> E[dp[m][n] = dp[m][m]]
D --> F[情况1: 有空盘子]
D --> G[情况2: 无空盘子]
F --> H[dp[m][n-1]]
G --> I[每个盘子至少1个]
I --> J[dp[m-n][n]]
H --> K[状态合并]
J --> K
E --> K
K --> L[dp[m][n] = dp[m][n-1] + dp[m-n][n]]
graph TD
A[DP表初始化] --> B[边界条件]
B --> C[dp[0][j] = 1]
B --> D[dp[i][1] = 1]
E[填表过程] --> F[按行填充]
F --> G[i: 1 to m]
G --> H[j: 1 to n]
H --> I[应用转移方程]
C --> J[状态转移]
D --> J
I --> J
J --> K[最终结果dp[m][n]]
flowchart TD
A[读取m和n] --> B[初始化DP表]
B --> C[设置边界条件]
C --> D[dp[0][j] = 1, dp[i][1] = 1]
D --> E[外层循环: i从1到m]
E --> F[内层循环: j从2到n]
F --> G{i < j?}
G -->|是| H[dp[i][j] = dp[i][i]]
G -->|否| I[dp[i][j] = dp[i][j-1] + dp[i-j][j]]
H --> J{内层循环结束?}
I --> J
J -->|否| F
J -->|是| K{外层循环结束?}
K -->|否| E
K -->|是| L[输出dp[m][n]]
graph TD
A[示例1: 3个苹果, 2个盘子] --> B[DP表构建]
B --> C[dp[0][1]=1, dp[0][2]=1]
B --> D[dp[1][1]=1, dp[2][1]=1, dp[3][1]=1]
C --> E[dp[1][2] = dp[1][1] = 1]
D --> F[dp[2][2] = dp[2][1] + dp[0][2] = 1+1 = 2]
E --> G[dp[3][2] = dp[3][1] + dp[1][2] = 1+1 = 2]
F --> H[结果验证]
G --> H
H --> I[方案: (0,3), (1,2)]
动态规划实现:
状态转移优化:
边界处理:
graph TD
A[递归解法] --> B[函数定义]
B --> C[solve(m, n)]
C --> D{递归基}
D -->|m=0 or n=1| E[返回1]
D -->|m|其他| G[solve(m, n-1) + solve(m-n, n)]
H[动态规划优势] --> I[避免重复计算]
I --> J[自底向上构建]
J --> K[时间复杂度更优]
graph TD
A[数学原理] --> B[整数分拆]
B --> C[分拆函数p(n,k)]
C --> D[n个数分成k组]
E[组合数学] --> F[相同物品排列]
F --> G[无序分组]
G --> H[分组大小统计]
D --> I[递推关系]
H --> I
I --> J[p(n,k) = p(n,k-1) + p(n-k,k)]
这个问题的关键在于正确理解整数分拆的本质和设计合理的状态转移方程,通过动态规划高效求解相同物品的分组方案数。
package main
import "fmt"
// 解决放苹果问题的动态规划函数
func countWays(m, n int) int {
// 创建二维DP表
// dp[i][j] 表示将i个苹果放入j个盘子的方案数
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
// 边界条件初始化
// 0个苹果只有一种放法(都不放)
for j := 1; j <= n; j++ {
dp[0][j] = 1
}
// 只有一个盘子,只有一种放法(全放一个盘子)
for i := 0; i <= m; i++ {
dp[i][1] = 1
}
// 填充DP表
for i := 1; i <= m; i++ {
for j := 2; j <= n; j++ {
if i < j {
// 苹果数小于盘子数,必有空盘子
// 等价于将i个苹果放入i个盘子
dp[i][j] = dp[i][i]
} else {
// 苹果数大于等于盘子数,分两种情况:
// 1. 有空盘子:dp[i][j-1]
// 2. 无空盘子(每个盘子至少放一个):dp[i-j][j]
dp[i][j] = dp[i][j-1] + dp[i-j][j]
}
}
}
return dp[m][n]
}
// 递归解法(供参考,理解递推关系)
func countWaysRecursive(m, n int) int {
// 边界条件
if m == 0 || n == 1 {
return 1
}
// 苹果数小于盘子数,等价于m个苹果放m个盘子
if m < n {
return countWaysRecursive(m, m)
}
// 递推关系:有空盘子的情况 + 无空盘子的情况
return countWaysRecursive(m, n-1) + countWaysRecursive(m-n, n)
}
func main() {
var m, n int
// 读取输入
fmt.Scan(&m, &n)
// 计算方案数
result := countWaysRecursive(m, n)
// 输出结果
fmt.Println(result)
}