信息素养大赛Python挑战赛往年第六题详解
分钱方案(往年信息素养复赛真题)
题目描述
有n个人,他们需要分配m元钱(m>n),每个人至少分到1元钱,且每个人分到的钱数必须是整数。请问有多少种分配方案?
输入描述
输入一行两个正热数n,m,用空格间隔。
输出描述
输出分配方案数。
样例输入
5 10
样例输出
126
问题分析
- 初始状态:
- 如果没有人(即i=0),那么没有方案,方案数为0。
- 如果没有钱(即j=0),那么唯一的方案就是所有人都分到 0元钱,但这种情况不符合每个人至少1 元钱的条件,方案数为0。
如下表所示:
- 转移方程
如i=3,j=5,代表我们将5元钱分给3个人,方案数用f(i,j)表示,所有方案如下:
- 最后一人分1元,其余两人分剩余的4元,方案数为f(i-1, j-1);
- 最后一人分2元,其余两人分剩余的3元,方案数为f(i-1, j-2);
- 最后一人分3元,其余两人分剩余的2元,方案数为f(i-1, j-3);
- 最后一人分4元,其余两人分剩余的1元。不符合要求,方案数为0。
- 最后一人分5元,其余两人分剩余的0元。不符合要求,方案数为0。
综上所述,方案数f(i,j) = f(i-1,j-1) + f(i-1, j - 2) + … + f(i -1, i-1)
因为 f(i-1, j - 2) + … + f(i -1, i-1) = f(i, j-1)所以状态转移方程为:f(i,j) = f(i-1,j-1) + f(i, j-1)
3.边界条件:
我们定义一个二维列表dp ,其中dp[i][j]表示将j元钱分配给i个人的方案数。
- dp[1][1]=1表示1个人,1元钱,只有一种方案。
- m<n时,钱数少于人数,方案数为0。
参考代码【递归】
def f(n, m):
if m < n:
return 0
if n == 1:
return 1
count = 0
for i in range(1, m - n + 2):# 递归计算 f(i-1,j-1) **+ f(i-1, j - 2) + ... + f(i -1, i-1)
# i的值最大为m-n+1
count += f(n - 1, m - i)# 从f(n-1, m-1) 到 f(n-1, m-(m-n+1))即f(n-1,n-1)累加求和
return count
n, m =map(int,input().split())
print(f(n, m))
参考代码篇【动态规划】
n, m = map(int,input().split())
dp =[[0]*(m+1) for i in range(n+1)]
for j in range(1, m+1):
dp[1][j]=1 # 大于等于1元时,只有1人分配方案有1种
for i in range(2, n+1):
for j in range(i, m+1):# 从i开始,j小于i不需要计算
dp[i][j]= dp[i-1][j-1] + dp[i][j-1]
print(dp[n][m])
递归和动态规划是解决很多算法问题的两种重要方法,尤其在处理需要重复子问题求解的问题时非常有效。尽管它们在某些方面相似,但在效率、内存使用以及实现方式上有着显著的区别。
递归
递归是一种通过函数调用自身来解决问题的方法。递归通常用于解决可以分解为相似子问题的问题,其中每个子问题都是原问题的一个更小版本。递归的关键在于找到一个基准情况(或称为基本情况),它是递归的停止条件,确保递归调用能够终止。
优点
简洁:代码通常比迭代方法更简洁,更容易理解。 通用:适用于许多类型的问题,尤其是那些可以自然分解为子问题的问题。
缺点
可能导致大量的重复计算:如果没有适当的记忆化或缓存机制,相同的子问题可能会被多次计算。 栈溢出风险:对于深层递归,可能会耗尽调用栈空间。
动态规划
动态规划是另一种解决优化和计数问题的方法,它通过将原问题分解为相对简单的子问题的方式求解,并且存储子问题的解以避免重复计算。动态规划通常与填表法(或称为自底向上方法)和记忆化递归(自顶向下加记忆化)相关联。
优点
高效:通过避免重复计算,动态规划通常比简单的递归实现要快得多。
易于实现记忆化:对于递归方法,添加记忆化可以显著提高效率,使其更接近动态规划的效率。
缺点
可能需要额外的空间来存储子问题的解。 问题分解和状态定义需要一定的技巧和经验。
总结
对于小规模问题或易于理解的问题,递归可能是更好的选择。然而,对于需要处理大量数据或追求高性能的场景,动态规划通常是更好的选择。在某些情况下,记忆化递归可以作为一种折衷方案,它结合了递归的简洁性和动态规划的效率。