放苹果

GitHub跳转原题关系图返回列表

按最后一个盘子是否为空做不重不漏二分:空则少一个盘子,非空则每个盘子各垫一个苹果,得 f(m,n)=f(m,n-1)+f(m-n,n),记忆化求整数拆分数 p_{≤n}(m)。

OJ: roj

题目 ID: 1192

难度:普及-

标签:入门动态规划计数DP记忆化搜索python

创建: 2026-09-29 22:50

更新: 2026-10-05 05:11

题目描述

把 MM 个同样的苹果放进 NN 个同样的盘子里,允许有的盘子空着,求不同的分法数 KK;5,1,15,1,1 与 1,5,11,5,1 是同一种分法。输入:第一行是测试数据数目 tt(0⩽t⩽200 \leqslant t \leqslant 20),以下每行两个整数 MM 和 NN,以空格分开(1⩽M,N⩽101 \leqslant M, N \leqslant 10)。输出:对每组数据一行输出相应的 KK。样例输入 1 与 7 3 对应输出 8。

思路

设 f(m,n)f(m,n) 为 mm 个苹果放进 nn 个盘子的分法数,按最后一个盘子是否为空不重不漏地分两类:空则少一个盘子得 f(m,n−1)f(m,n-1);非空则非降序下所有盘子都非空,每盘先垫一个苹果得 f(m−n,n)f(m-n,n)。故 f(m,n)=f(m,n−1)+f(m−n,n)f(m,n)=f(m,n-1)+f(m-n,n),边界 f(0,n)=f(m,1)=1f(0,n)=f(m,1)=1,m<nm<n 时多余盘子必然空着可折叠为 f(m,m)f(m,m),记忆化递推即可。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-07-05 22:10
# update_at: 2026-07-05 22:10

import sys
from functools import cache


@cache
def count(m: int, n: int) -> int:
    """m 个同样的苹果放进 n 个同样的盘子(允许空盘)的方案数。

    边界:没有苹果、或只剩一个盘子,都只有一种放法。
    """
    if m == 0 or n == 1:
        return 1
    if m < n:
        return count(m, m)  # 盘子比苹果多,多出的盘子必然空着,与 m 个盘子等价
    # 按第 n 个盘子是否为空分类:空 → 少一个盘子;不空 → 每盘先垫 1 个
    return count(m, n - 1) + count(m - n, n)


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    T = next(data)

    out: list[str] = []
    for _ in range(T):
        m, n = next(data), next(data)  # 苹果数 m,盘子数 n
        out.append(str(count(m, n)))

    print('\n'.join(out))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 05:11
 * update_at: 2026-10-05 05:11
 */

#include <cstdio>

typedef long long ll;

ll memo[15][15]; // memo[m][n] = m 个苹果放进 n 个盘子的分法数,-1 表示还没算过

// 求把 m 个同样的苹果放进 n 个同样的盘子(允许空盘)的分法数
ll count_ways(ll m, ll n) {
    if (memo[m][n] != -1) return memo[m][n]; // 记忆化:每个状态只算一次
    ll res;
    if (m == 0 || n == 1) {
        res = 1; // 没有苹果(全空)或只剩一个盘子,都只有一种放法
    } else if (m < n) {
        res = count_ways(m, m); // 盘子比苹果多,多出的盘子必然空着,与 m 个盘子等价
    } else {
        // 按第 n 个盘子是否为空分类,两类互斥且不漏:
        // 空   -> 少一个盘子:count_ways(m, n-1)
        // 非空 -> 非降序下所有盘子都非空,每盘先垫 1 个苹果:count_ways(m-n, n)
        res = count_ways(m, n - 1) + count_ways(m - n, n);
    }
    memo[m][n] = res;
    return res;
}

int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        ll m, n;
        scanf("%lld %lld", &m, &n);
        // 多组数据共用一张记忆化表:先把会用到的范围清成 -1
        for (ll i = 0; i <= m; ++i)
            for (ll j = 1; j <= n; ++j)
                memo[i][j] = -1;
        printf("%lld\n", count_ways(m, n));
    }
    return 0;
}