UVa 10590 Boxes of Chocolates Again
本文讨论了整数划分问题,将正整数N表示为若干正整数之和的不同方式数。通过动态规划方法,将其视为完全背包问题求解,使用高精度处理大数运算。关键步骤包括初始化dp[0]=1,状态转移方程dp[j] += dp[j-k],以及实现BigInt类处理大数加法。算法复杂度为O(N² × L),适用于N≤5000的情况,能高效计算所有划分方式数。
题目描述
小皮皮在 777 岁生日时收到了很多巧克力盒子。她的父母只允许她拿 NNN 个巧克力。巧克力装在多种类型的盒子中,如果一个盒子包含 kkk 个巧克力,就称它为 type-k\text{type-}ktype-k 盒子。从 type-1\text{type-1}type-1 到 type-N\text{type-}Ntype-N 的每种盒子都有无限多个。皮皮必须正好拿 NNN 个巧克力,且不能拆开任何盒子。任务是计算皮皮有多少种不同的方式可以做到这一点。
例如,当 N=3N = 3N=3 时,有以下 333 种方式:
- 一个 type-3\text{type-3}type-3 盒子
- 一个 type-2\text{type-2}type-2 盒子和一个 type-1\text{type-1}type-1 盒子
- 三个 type-1\text{type-1}type-1 盒子
输入格式:多行,每行一个整数 NNN (0≤N≤5000)(0 \leq N \leq 5000)(0≤N≤5000)
输出格式:对于每个 NNN,输出方式数
题目分析
这个问题本质上是整数划分问题(Integer Partition\texttt{Integer Partition}Integer Partition),即求将正整数 NNN 表示为若干个正整数之和的不同方式数,其中顺序不重要。
关键观察
- 问题本质:将 NNN 划分为若干个正整数的和,划分的顺序不重要
- 无限供应:每种盒子数量无限,对应划分中每个数字可以使用多次
- 顺序无关:2+12+12+1 和 1+21+21+2 被视为同一种划分方式
样例分析
- N=3N = 3N=3:划分有 333、2+12+12+1、1+1+11+1+11+1+1,共 333 种
- N=4N = 4N=4:划分有 444、3+13+13+1、2+22+22+2、2+1+12+1+12+1+1、1+1+1+11+1+1+11+1+1+1,共 555 种
- N=5N = 5N=5:划分有 555、4+14+14+1、3+23+23+2、3+1+13+1+13+1+1、2+2+12+2+12+2+1、2+1+1+12+1+1+12+1+1+1、1+1+1+1+11+1+1+1+11+1+1+1+1,共 777 种
解题思路
动态规划方法
这是一个经典的完全背包问题:
- 物品:重量为 1,2,3,…,N1, 2, 3, \ldots, N1,2,3,…,N 的盒子
- 背包容量:NNN
- 目标:求恰好装满背包的方案数
状态定义
设 dp[i]dp[i]dp[i] 表示整数 iii 的划分数(即方式数)。
初始状态
dp[0]=1dp[0] = 1dp[0]=1,表示空划分(不选任何盒子)。
状态转移方程
对于每个可能的盒子类型 kkk(从 111 到 NNN),我们考虑将其加入划分中:
dp[j]=dp[j]+dp[j−k]for j=k to Ndp[j] = dp[j] + dp[j - k] \quad \text{for } j = k \text{ to } Ndp[j]=dp[j]+dp[j−k]for j=k to N
这个转移确保了我们按非降序生成划分,避免了重复计数。
算法步骤
- 初始化 dp[0]=1dp[0] = 1dp[0]=1,其余为 000
- 对于 kkk 从 111 到 NNN:
- 对于 jjj 从 kkk 到 NNN:
- dp[j]+=dp[j−k]dp[j] += dp[j - k]dp[j]+=dp[j−k]
- 对于 jjj 从 kkk 到 NNN:
- 对于每个查询,输出 dp[N]dp[N]dp[N]
大数处理
由于 N=5000N = 5000N=5000 时的划分数非常大(有几百位),需要使用高精度运算。我们实现一个简单的 BigInt\texttt{BigInt}BigInt 类来处理大数加法。
复杂度分析
- 时间复杂度:O(N2×L)O(N^2 \times L)O(N2×L),其中 LLL 是大数的平均位数
- 空间复杂度:O(N×L)O(N \times L)O(N×L),存储所有状态的大数值
代码实现
// Boxes of Chocolates Again
// UVa ID: 10590
// Verdict: Accepted
// Submission Date: 2025-11-10
// UVa Run Time: 0.320s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
class BigInt {
private:
vector<int> digits;
static const int BASE = 10000;
static const int BASE_DIGITS = 4;
public:
BigInt() : digits(1, 0) {}
BigInt(long long num) {
if (num == 0) digits.push_back(0);
while (num > 0) {
digits.push_back(num % BASE);
num /= BASE;
}
}
BigInt& operator+=(const BigInt& other) {
int carry = 0;
int maxSize = max(digits.size(), other.digits.size());
digits.resize(maxSize, 0);
for (int i = 0; i < maxSize; i++) {
int sum = digits[i] + (i < other.digits.size() ? other.digits[i] : 0) + carry;
digits[i] = sum % BASE;
carry = sum / BASE;
}
if (carry > 0) {
digits.push_back(carry);
}
return *this;
}
friend ostream& operator<<(ostream& os, const BigInt& num) {
os << num.digits.back();
for (int i = num.digits.size() - 2; i >= 0; i--) {
os << setw(BASE_DIGITS) << setfill('0') << num.digits[i];
}
return os;
}
};
int main() {
const int MAX_N = 5000;
vector<BigInt> dp(MAX_N + 1);
dp[0] = BigInt(1);
for (int maxNum = 1; maxNum <= MAX_N; maxNum++) {
for (int j = maxNum; j <= MAX_N; j++) {
dp[j] += dp[j - maxNum];
}
}
int n;
while (cin >> n) {
cout << dp[n] << endl;
}
return 0;
}
总结
本题通过动态规划结合高精度运算解决了大范围的整数划分问题。关键点在于:
- 正确识别问题为整数划分/完全背包问题
- 使用动态规划避免重复计数
- 实现高效的大数运算处理极大结果
算法能够正确处理 N≤5000N \leq 5000N≤5000 的所有情况,在时间和空间复杂度上都是可行的。
更多推荐


所有评论(0)