1. 项目背景与题目解析
"宠物小精灵之收服"是《信息学奥赛一本通》中的一道经典动态规划题目(编号1292)。这道题源自经典的宠物小精灵(宝可梦)游戏设定,要求参赛者在有限的精灵球数量和训练师体力条件下,计算出能够收服最多小精灵的方案。
题目本质是一个二维背包问题的变种:
- 第一维限制:拥有的精灵球数量(类比背包容量)
- 第二维限制:训练师的体力值(新增约束维度)
- 目标:在不超过精灵球数量和体力消耗的前提下,收服最多数量的小精灵
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题建模与算法选择
2.1 题目参数定义
设:
- 总精灵球数量为M
- 训练师初始体力为K
- 有N只野生小精灵
- 收服第i只小精灵需要消耗a[i]个精灵球和b[i]点体力
2.2 动态规划状态设计
采用三维数组dp[i][j][k]表示:
- 考虑前i只小精灵
- 使用j个精灵球
- 消耗k点体力
时的最大收服数量
状态转移方程:
code复制dp[i][j][k] = max(
dp[i-1][j][k], // 不收服第i只
dp[i-1][j-a[i]][k-b[i]] + 1 // 收服第i只
)
2.3 空间优化技巧
实际编码时可优化为二维数组:
cpp复制int dp[M+1][K+1] = {0};
for(int i=1; i<=N; i++){
for(int j=M; j>=a[i]; j--){
for(int k=K; k>=b[i]; k--){
dp[j][k] = max(dp[j][k], dp[j-a[i]][k-b[i]]+1);
}
}
}
3. 边界条件与特殊处理
3.1 体力耗尽判定
题目要求收服后体力不能归零(必须>0),因此:
- 实际可用体力为K-1
- 状态转移时k的范围是1到K-1
3.2 最优解后处理
在得到最大收服数量max_cnt后,需要逆向求解消耗的最小体力:
cpp复制int min_cost = K;
for(int k=0; k<K; k++){
if(dp[M][k] == max_cnt){
min_cost = min(min_cost, k);
}
}
4. 完整代码实现(C++)
cpp复制#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int M, K, N;
cin >> M >> K >> N;
int a[101], b[101];
for(int i=1; i<=N; i++){
cin >> a[i] >> b[i];
}
int dp[1001][501] = {0}; // dp[精灵球][体力] = 最大数量
for(int i=1; i<=N; i++){
for(int j=M; j>=a[i]; j--){
for(int k=K-1; k>=b[i]; k--){ // 注意体力不能耗尽
dp[j][k] = max(dp[j][k], dp[j-a[i]][k-b[i]]+1);
}
}
}
int max_cnt = dp[M][K-1];
int min_cost = K;
for(int k=0; k<K; k++){
if(dp[M][k] == max_cnt){
min_cost = min(min_cost, k);
}
}
cout << max_cnt << " " << (K - min_cost) << endl;
return 0;
}
5. 算法优化与变种思考
5.1 时间复杂度分析
原始解法:
- 时间复杂度:O(NMK)
- 空间复杂度:O(M*K)
当M和K较大时(如1e4量级),可能需要进一步优化:
- 滚动数组优化空间
- 根据数据特点剪枝
5.2 变种问题举例
- 求具体收服方案(记录路径)
- 每只小精灵有不同价值,求最大价值
- 精灵球和体力可以补充的特殊规则
6. 实战注意事项
- 输入格式陷阱:题目可能给出多余的空格或换行,建议使用cin自动处理
- 数组越界防护:确保所有数组访问在合法范围内
- 初始化重要性:dp数组必须初始化为0
- 逆向枚举原因:背包问题必须逆向枚举以避免重复计算
- 体力边界检查:特别注意K-1的边界条件
提示:在竞赛中遇到类似题目时,建议先手写几个小规模测试案例验证算法正确性,再处理大规模输入。
