岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N 块能量石准备开吃。
由于他的嘴很小,所以一次只能吃一块能量石。
能量石很硬,吃完需要花不少时间。
吃完第 i 块能量石需要花费的时间为 Si 秒。
杜达靠吃能量石来获取能量。
不同的能量石包含的能量可能不同。
此外,能量石会随着时间流逝逐渐失去能量。
第 i 块能量石最初包含 Ei 单位的能量,并且每秒将失去 Li 单位的能量。
当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。
能量石中包含的能量最多降低至 0。
请问杜达通过吃能量石可以获得的最大能量是多少?
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N,表示能量石的数量。
接下来 N 行,每行包含三个整数 Si,Ei,Li
。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y
,其中 x 是组别编号(从 1 开始),y 是可以获得的最大能量值。
数据范围
1≤T≤10, 1≤N≤100, 1≤Si≤100, 1≤Ei≤105, 0≤Li≤105
输入样例:
3
4
20 10 1
5 30 5
100 30 1
5 80 60
3
10 4 1000
10 3 1000
10 8 1000
2
12 300 50
5 200 0
输出样例:
Case #1: 105
Case #2: 8
Case #3: 500
思路
对于一个复杂问题, 难以求解时就寻找有没有性质可以缩小最优解范围。
对于每个石头, 我们没必要同时处理所有, 显然当一个石头能量值为0时便不用花时间去吃。 性质1: 只吃能量还存在的石头 这个性质显而易见, 接下来就选两个石头看怎么选择 对于这两个石头都吃时所获得的能量有两个情况:
- 先吃
i
后吃i+1
e[i] + e[i + 1] - s[i] * l[i + 1]
- 先吃
i+1
后吃i
e[i+1] + e[i] - s[i+1] * l[i]
显然, 这里有个最优选择, 选择 s 最小的那个先吃。
也就是说, 对于所有可行吃法, 最优解肯定符合, 吃的顺序是s[i] * l[i+1]
从小到大的。
性质2: 吃石头的最优解中满足任意两个石头, 先吃的 s[i] * l[i + 1] < s[i + 1] * l[i]
那么我们就将所有石头排个序, 对于石头i, 不可能再从i之前的石头中得到比当前更优的解法, 故 f[i - 1]
是固定的, 不会因后续的新石头而变化
因此, 就跟01背包问题相类似了。
带入01背包的框架思考一下:
对于石头i有选和不选两种选择
选: f[i][j] = f[i - 1][j - s[i]] + e[i] - (j - s[i])*l[i];
不选: f[i][j] = f[i - 1][j];
这样的话有个问题, 题目说开始吃的时候能量就不会减少了, 会不会出现能吃但因为时间不够而吃不了呢? 并不会, 题目没有对吃的时间做限制, 也就是说, 哪怕当前的j时间吃不了, 之后也会更大的能吃得了的j。
如果对总时间做了约束, 会更复杂一些。
这里既然是01背包那么也能优化为一维。
初始化时应该初始化为负无穷, 时间恰好是j: 以前能算到至多是 j 是因为dp[0][j]都是0,即从i=0时从哪个体积开始都是合法的, 相当于把空余的体积加在了最前面,但现在这样不是最优,把空余时间加在最后面和最前面不一样。 多的时间会导致能量随时间减少。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, T = 1e7 + 10;
int f[T];
int n;
struct Stone
{
int s,e,l;
bool operator < (const Stone &W)const
{
return s * W.l < W.s * l;
}
}stone[N];
int main()
{
int k, kase=0;
cin >> k;
while(k--)
{
int m = 0;
cin >> n;
for(int i = 0; i < n; i++)
{
int s,e,l;
cin >> s >> e >> l;
stone[i] = {s,e,l};
m += s; // 最大用时就是把所有石头都吃掉
}
sort(stone, stone + n);
memset(f, 0, sizeof f);
for(int i = 0; i < n; i++)
{
int s = stone[i].s, e = stone[i].e, l = stone[i].l;
for(int j = m; j >= s; j--)
{
f[j] = max(f[j], f[j - s] + e - (j - s) * l);
//cout << s << " " << e << " " << l << " " << f[j] << endl;
}
}
int res = 0;
for(int i = 0; i <= m; i++)
res = max(res, f[i]);
printf("Case #%d: %d\n", ++kase, res);
}
return 0;
}