原题链接:https://www.acwing.com/problem/content/description/736/
难度:困难(?)
涉及知识点:01背包、贪心算法
给定一些能量石,对于第 i i i 块能量石,其初始能量值为 E i E_i Ei,每秒会减少 L i L_i Li 单位的能量,吃完这块能量石需要 S i S_i Si 秒并且会立刻得到这块能量石的能量。能量石的的能量最多降到 0,不会为负数。求杜达最后能获得多少能量。
这一题乍一看确实是没有思路的,但是我们细想一下,在基于闫氏DP思考法即在集合角度思考DP 问题时,往往是考虑了整个集合,那么存不存在一个子集,让我们的注意力都集中在某一个子集中呢?当然有,这个子集就是最优解子集。
考虑在最优解子集
a
a
a 中, 元素的排列为
a
1
,
a
2
,
a
3
,
a
4
,
a
5
,
.
.
.
,
a
n
−
2
,
a
n
−
1
,
a
n
a_1,a_2,a_3,a_4,a_5,...,a_{n-2},a_{n-1},a_n
a1,a2,a3,a4,a5,...,an−2,an−1,an
对于一组相邻的元素
a
i
a_i
ai 和
a
j
a_j
aj,如果我先吃
a
i
a_i
ai 再吃
a
j
a_j
aj,那么所获取的能量为
E
i
+
E
j
−
S
i
×
L
j
E_i+E_j-S_i\times L_j
Ei+Ej−Si×Lj;反之所获得的能量为
E
j
+
E
i
−
S
j
×
L
i
E_j+E_i-S_j\times L_i
Ej+Ei−Sj×Li,由于这是最优解子集,我们必然可以得出如下不等式
E
i
+
E
j
−
S
i
×
L
j
≥
E
j
+
E
i
−
S
j
×
L
i
①
E_i+E_j-S_i\times L_j\geq E_j+E_i-S_j\times L_i①
Ei+Ej−Si×Lj≥Ej+Ei−Sj×Li①
整理得
S
i
×
L
j
≤
S
j
×
L
i
②
S_i\times L_j\leq S_j\times L_i ②
Si×Lj≤Sj×Li②
逆向推理,也就是如果一个集合能够满足这两个不等式,就一定是最优解子集,那么我们就可以在输入数据出通过
s
o
r
t
sort
sort 排序构造出最优解子集。然后在最优解子集里做 01 背包即可。
#include
#include
#include
#include
using namespace std;
const int N = 1e4 + 10;
struct Stone
{
int s, e, l;
bool operator< (const Stone &W) const
{
return s * W.l < l * W.s;
}
}stone[N];
int T, n;
int f[N];
int main()
{
cin >> T;
for (int C = 1; C <= T; C++)
{
int m = 0;
cin >> n;
for (int i = 1; i <= n; i++)
{
int s, e, l;
cin >> s >> e >> l;
stone[i] = {s, e, l};
m += s;
}
sort(stone + 1, stone + n + 1);
memset(f, -0x3f3f3f3f, sizeof f);
f[0] = 0;
for (int i = 1; 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);
}
}
int res = 0;
for (int i = 0; i <= m; i++) res = max(res, f[i]);
printf("Case #%d: %d\n", C, res);
}
return 0;
}