0. 前言
相关:
1. 01背包裸题
每个精灵仅被收服一次,故可以考虑 01 背包,是典型二维费用套 01 背包裸题。
思路:
- 在此有两个花费和一个价值
- 花费1:精灵球数量
- 花费2:皮卡丘体力值
- 价值:已收服小精灵的数量。每个小精灵价值为 1
- 状态表示:
f[i,j,k]
:所有只从前i
个精灵中选,且花费 1 不超过j
,花费 2 不超过k
的最大收服精灵数目
- 状态计算:
- 和 01 背包一致,根据最后一步第
i
个物品选还是不选分类 f[i,j,k] = max(f[i - 1,j,k],f[i-1,j-v1[i],k-v2[i]]+1)
- 优化至二维:
f[j][k] = max(f[j][k],f[j-v1[i]][k-v2[i]]+1)
。但需要注意枚举j、k
的时候需要逆序枚举,本质还是 01 背包问题
- 和 01 背包一致,根据最后一步第
- 答案:
- 最多收服的小精灵数量:
f[K,N,M]
- 最少耗费体力,从
K-1
开始枚举到 0,找到收服数量为f[K,N,M]
的最小值k
,f[K,N,t]==f[K,N,M]
- 题目要求:使得皮卡丘体力小于等于 0 的野生小精灵也不会被收服,,所以皮卡丘的体力需要从
V2-1
开始
- 最多收服的小精灵数量:
01 背包二维费用,优化掉 i
,三维变两维,体积从大到小进行枚举。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1005, M = 505, K = 105;
int n, m, t;
int f[N][M];
int main() {
cin >> n >> m >> t;
for (int i = 0; i < t; ++i) {
int a, b;
cin >> a >> b;
for (int j = n; j >= a; --j)
for (int k = m - 1; k >= b; --k)
f[j][k] = max(f[j][k], f[j - a][k - b] + 1);
}
cout << f[n][m - 1] << ' ';
int k = m - 1;
while (k > 0 && f[n][k - 1] == f[n][m - 1]) k --;
cout << m - k << endl;
return 0;
}
来自大佬优化:1022. 宠物小精灵之收服 体积与价值选择问题效率对比
- 在背包问题中,体积
w
与价值v
是可以互逆的 - 可以将
f[i]
表示为体积为i
能装的最大价值,也可以将f[i]
表示为价值为i
所需的最小体积。两者等价,只需要选择范围较小的那维作为体积就可以了!这直接影响到时空复杂度。这题就是个案例。
上面是以体力、精灵球数为费用、精灵数为价值,
O
(
n
m
k
)
O(nmk)
O(nmk) f[i][j]
表示为体力为 i
,精灵球数为 j
所收集到的最大精灵。
差不多是 5e7
的计算量。
本题 k
是很小的,所有可以以体力、精灵数为费用,精灵球数为代价,则为
O
(
k
2
m
)
O(k^2m)
O(k2m)
f[i][j]
表示体力为 i
,收集了 j
个精灵,且使用最小的精灵球数量。
差不多是 5e6
的计算量。优化了 10 倍。
优化代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1005, M = 505, K = 105;
const int INF = 0x3f3f3f3f;
int n, m, t;
int f[M][K];
int main() {
cin >> n >> m >> t;
memset(f, 0x3f, sizeof f);
for (int i = 0; i < t; ++i) f[i][0] = 0; // 不收服精灵,则不用精灵球,故初始化为0
for (int i = 0; i < t; ++i) {
int a, b;
cin >> a >> b;
for (int j = m; j >= b; --j)
for (int k = t; k >= 1; --k)
if (f[j - b][k - 1] + a <= n)
f[j][k] = min(f[j][k], f[j - b][k - 1] + a);
}
for (int k = t; k >= 0; --k) { // 逆序枚举精灵最大只数
int p = INF;
for (int j = 0; j < m; ++j) // 体积从0~m-1,顺序枚举
if (f[j][k] != INF && j < p)// 若发生转移且体积有更新,则获得最小体积下的最大收服精灵数
p = j;
if (p != INF) {
cout << k << ' ' << m - p << endl;
return 0;
}
}
return 0;
}