把划分数写成 (剩余和, 下一段上限) 的记忆化递归:枚举首段后把上限收紧为它,非增约定天然去重,指数枚举降到 O(n³)。
OJ: roj
题目 ID: 5000
难度:入门
标签:递归记忆化搜索整数划分计数python
创建: 2026-10-02 15:16
更新: 2026-10-06 16:34
题目描述
给定正整数
思路
完全背包计数:令
参考代码
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-10-02 15:22
# update_at: 2026-10-02 15:22
import sys
from functools import cache
@cache
def parts(rest: int, cap: int) -> int:
"""把 rest 拆成若干不超过 cap 的正整数之和(顺序无关)的方案数。
枚举首段 x,再把 rest-x 交给同一个问题、上限收紧为 x:
下一段永远不超过上一段,5+1 与 1+5 落进同一条分支,天然不重不漏。
"""
if rest == 0:
return 1 # 空拆分:各段恰好拼完,算一种方案
return sum(parts(rest - x, x) for x in range(min(rest, cap), 0, -1))
def solve() -> None:
n = int(sys.stdin.buffer.read())
print(parts(n, n))
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-06 16:30
* update_at: 2026-10-06 16:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1005; // 题目测试数据 n 不大,状态表开到 1000 足够
// memo[r][c] 表示把 r 拆成若干不超过 c 的正整数之和的方案数;-1 表示未计算
ll memo[MAXN][MAXN];
// parts(r, c):把 r 拆成若干不超过 c 的正整数之和的方案数
ll parts(int r, int c) {
if (r == 0) return 1; // 已经拼完,空拆分算一种
if (c <= 0) return 0; // 还有剩余但上限为 0,无法拆分
ll &res = memo[r][c];
if (res != -1) return res;
res = 0;
int up = min(r, c);
for (int x = 1; x <= up; x++) {
// 首段取 x,下一段上限收紧为 x
res += parts(r - x, x);
}
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) return 0;
// 只清理实际需要使用的状态范围
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
memo[i][j] = -1;
}
}
cout << parts(n, n) << "\n";
return 0;
}