欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 八卦 > 一道经典的整数划分题——分弹珠

一道经典的整数划分题——分弹珠

2024/11/30 8:42:22 来源:https://blog.csdn.net/m0_74412436/article/details/144095624  浏览:    关键词:一道经典的整数划分题——分弹珠

CSDN 博客:一道经典的整数划分题——分弹珠

一、题目描述

这道题目是一道经典的整数划分问题,要求将 (M) 个弹珠分到 (N) 个盘子中,满足以下条件:

  • 允许盘子为空。
  • 两种分法被认为相同当且仅当分配的弹珠数量相同(不考虑顺序)。

例如,将 (7) 个弹珠分到 (3) 个盘子中的方法数是 (8)。如果不考虑盘子顺序,分法包括:
( 7 , 0 , 0 ) , ( 6 , 1 , 0 ) , ( 5 , 2 , 0 ) , ( 5 , 1 , 1 ) , ( 4 , 3 , 0 ) , ( 4 , 2 , 1 ) , ( 3 , 3 , 1 ) , ( 3 , 2 , 2 ) 。 (7,0,0), (6,1,0), (5,2,0), (5,1,1), (4,3,0), (4,2,1), (3,3,1), (3,2,2)。 (7,0,0),(6,1,0),(5,2,0),(5,1,1),(4,3,0),(4,2,1),(3,3,1),(3,2,2)

二、整数划分题的核心思想

整数划分是组合数学中的经典问题。整数划分问题的核心在于:

  • 如果我们用 P ( m , n ) P(m, n) P(m,n)表示将 m m m个物品分到 n n n个容器中的分法数,那么可以利用递推关系解决问题。
递推公式推导

考虑将 M M M 个弹珠分到 N N N个盘子中:

  1. 如果我们规定 盘子 N N N为空,那么问题就转化为将 M M M个弹珠分到 N − 1 N-1 N1 个盘子中,即 P ( m , n − 1 ) P(m, n-1) P(m,n1)
  2. 如果盘子 N N N 不为空,我们至少需要放一个弹珠,那么问题就转化为将剩下的 M − N M-N MN个弹珠分到 N N N个盘子中,即 P ( m − n , n ) P(m-n, n) P(mn,n)

因此,递推公式为:
P ( m , n ) = P ( m , n − 1 ) + P ( m − n , n ) , P(m, n) = P(m, n-1) + P(m-n, n), P(m,n)=P(m,n1)+P(mn,n),
同时考虑到边界条件:

  • 当 (m = 0) 时,只有一种分法,即所有盘子均为空:
    P ( 0 , n ) = 1 P(0, n) = 1 P(0,n)=1
  • 当 (n = 0) 且 (m > 0) 时,分法数为 (0):
    P ( m , 0 ) = 0 P(m, 0) = 0 P(m,0)=0
  • 当 (n > m) 时,盘子的数量大于弹珠数量,此时最多只需 (m) 个盘子,因此:
    P ( m , n ) = P ( m , m ) P(m, n) = P(m, m) P(m,n)=P(m,m)
三、两种解法详解

方法一:带备忘录的递归

带备忘录的递归利用了记忆化搜索的思想,可以避免重复计算。代码如下:

#include <iostream>
#include <cstring>
using namespace std;int memo[21][21]; // 备忘录,用于存储中间结果// 递归计算 P(m, n)
int P(int m, int n) {if (m == 0) return 1;          // 边界条件:0个弹珠只有1种分法if (m < 0 || n == 0) return 0; // 边界条件:盘子用完或弹珠数量负数if (n > m) return P(m, m);     // 若盘子数多于弹珠数,限制盘子数为mif (memo[m][n] != -1) return memo[m][n]; // 如果已计算过,直接返回结果memo[m][n] = P(m, n-1) + P(m-n, n); // 递推公式return memo[m][n];
}int main() {memset(memo, -1, sizeof(memo)); // 初始化备忘录int t, M, N;while (cin >> t) {for (int i = 0; i < t; i++) {cin >> M >> N;cout << P(M, N) << endl;}}return 0;
}
解法分析
  • 时间复杂度:由于每个状态 P ( m , n ) P(m, n) P(m,n)只计算一次,总的复杂度为 O ( M × N ) O(M \times N) O(M×N)
  • 空间复杂度:需要额外的 O ( M × N ) O(M \times N) O(M×N) 空间存储中间结果。

方法二:分治法(动态规划)

动态规划通过表格存储所有子问题的解,从而避免了递归调用的开销。代码如下:

#include <iostream>
using namespace std;int dp[21][21]; // 动态规划数组// 初始化动态规划表
void initialize_dp() {for (int m = 0; m <= 20; m++) {for (int n = 0; n <= 20; n++) {if (m == 0)dp[m][n] = 1; // 边界条件else if (n == 0)dp[m][n] = 0; // 边界条件else if (n > m)dp[m][n] = dp[m][m]; // 当盘子数多于弹珠数elsedp[m][n] = dp[m][n-1] + dp[m-n][n]; // 递推公式}}
}int main() {initialize_dp(); // 初始化 DP 表int t, M, N;while (cin >> t) {for (int i = 0; i < t; i++) {cin >> M >> N;cout << dp[M][N] << endl;}}return 0;
}
解法分析
  • 时间复杂度:预处理的复杂度为 O ( M × N ) O(M \times N) O(M×N),查询的复杂度为 O ( 1 ) O(1) O(1)
  • 空间复杂度:需要 O ( M × N ) O(M \times N) O(M×N) 的存储空间。
四、总结

本题作为整数划分问题的典型代表,考察了递归与动态规划的灵活运用:

  • 递归方法(带备忘录)更容易直观理解,但需要注意栈的深度。
  • 动态规划方法 是实际中更高效且稳定的选择,特别是在多组测试的情况下。

整数划分的核心在于掌握 递推公式 的推导,并灵活运用 边界条件 简化问题的求解。希望本篇博客能够帮助读者对整数划分问题有更深入的理解!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com