OJ 百练 1664:放苹果(整数划分问题)

题目

总时间限制: 1000ms;内存限制: 65536kB

描述

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。

输入

第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。

输出

对输入的每组数据M和N,用一行输出相应的K。

样例输入

1
7 3

样例输出

8

来源

lwx@POJ

思路

        一种理解,构造一个划分函数q(n,m),n表示苹果的个数,m表示一个盘子中放置苹果的最大数量。将n个苹果进行划分,考虑在划分中最大加数为m,也就是限制某个盘子中最多的苹果数量是m,那么有两种情况,一种情况是确实存在这样一个盘子,其中苹果的数量是m,这时我们只需要考虑剩下的n-m个苹果进行划分,限制某个盘子中最多的苹果数量是m,也就是q(n-m,m);另一种情况是不存在其中苹果数量为m的盘子,也就是某个盘子中苹果的数量最多为m-1,问题转化为了q(n,m-1)。由此我们得到了这个问题的递推公式,或者说递归公式——q(n,m)=q(n,m-1)+q(n-m,m)

        显然,如果n=1或m=1,只有一个苹果,或者只有一个盘子,就只有一种方法,这是一个终止条件。如果盘子的限制放置数量m超过苹果的数量n,那么某个盘子中苹果数量最多为n,q(n,m)转化为了q(n,n)。如果盘子的限制放置数量m等于苹果的数量n,这时某一个盘子放n个苹果肯定是一种放法,且其他盘子没有苹果可放了,也可以限制一个盘子中最多放n-1个苹果,问题转化为q(n,n-1)。之所以这样讨论,也是因为可以避免q(n,m)的参数为0或负数的情况,方便统一描述问题。

        但,这种理解下,盘子的数量并不限于m个?不过我们可以这样想,一开始我们没有用盘子放苹果,只是把苹果把放在了一列列的表格中,表格的行数限制为m,最后才把不同行的苹果分别收集起来,放到对应行代表的盘子中。不限制盘子的数量转而限制每个盘子中苹果的数量,似乎绕了一个弯路,不过最终得到的还是苹果不同分法的数量K。或许这也有助于我们理解整数划分的问题。

        对划分函数q(n,m)的另一种理解是,n表示苹果的数量,m表示可以用来放置苹果的盘子的个数,允许有的盘子空着不放,q(n,m)即是求将n个苹果放到m个盘子中,有多少种不同的分法。这样的话,我们每次放苹果到盘子中,可以选择放,也可以选择不放。如果选择每个盘子都放苹果,那么每个盘子至少要放一个苹果,我们先认为这时每个盘子都放了1个苹果,还剩下n-m个苹果,有m个盘子可供放置,即需要求q(n-m,m);如果并非每个盘子都放苹果,那么至少要有1个盘子为空,也就是我们还剩下m-1个盘子可以用来放n个苹果,问题转化为了q(n,m-1)。所以,递推公式为:q(n,m)=q(n,m-1)+q(n-m,m)

        显然,如果n=1或m=1,只有一个苹果,或者只有一个盘子,就只有一种放置方法,这是一个终止条件。如果盘子的数量m多于苹果的数量n,那么实际上最多也只能用到n个盘子,问题变为q(n,n)。如果盘子的数量m等于苹果的数量n,一种简单的放法是每个盘子放1个苹果,正好放完;要么存在没有放苹果的盘子,也就是说至少有1个盘子没有放苹果,最多用了n-1个盘子来放苹果,问题转化为q(n,n-1)。

        当然,从题目的描述来看,后一种理解更合适理解题意,不过实际上两种理解最终得到的结果是一样的,也可以转换为等价的操作,其本质就是整数划分问题。

Python参考代码

递归形式

def q(n,m): #n>0,m>0
    if n==1 or m==1:
        return 1
    elif n<m:
        return q(n,n)
    elif n==m:
        return q(n,n-1)+1
    else:   #n>m>1
        return q(n,m-1)+q(n-m,m)

t=int(input())
M,N=map(int,input().split())
for i in range(t):
    print(q(M,N))

动态规划形式(初始化的设置和边界情况需要仔细考虑)

def dp_q(n, m):
    # 初始化 dp 数组为 0
    dp = [[0 for _ in range(m+1)] for _ in range(n+1)]
    
    # 边界条件
    for i in range(1, n+1):
        dp[i][1] = 1  # 将 i 划分为 1 个部分只有一种方式
        dp[i][0] = 0  # 无法将 i 划分为 0 个部分
    for j in range(1, m+1):
        dp[1][j] = 1  # 将 1 划分为 j 个部分只有一种方式
    
    # 处理 m > n 的情况
    if m > n:
        m = n
    
    # 递推关系
    for i in range(2, n+1):
        for j in range(2, m+1):
            if i <= j:
                dp[i][j] = dp[i][i-1] + 1
            else:
                dp[i][j] = dp[i][j-1] + dp[i-j][j]
    
    return dp[n][m]
   
# 输入处理   
t = int(input())   
for _ in range(t):
    M, N = map(int, input().split())
    print(dp_q(M, N))

参考链接

整数划分问题 - 菜菜籽的文章 - 知乎https://zhuanlan.zhihu.com/p/455145557

整数划分问题(分苹果)https://blog.csdn.net/frostime/article/details/79874589?fromshare=blogdetail&sharetype=blogdetail&sharerId=79874589&sharerefer=PC&sharesource=m0_62504307&sharefrom=from_link

递归算法《M个苹果放入N个盘子》递归算法《M个苹果放入N个盘子》_把m个同样的苹果放在n个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值