AcWing 734. 能量石
题目链接
https://www.acwing.com/problem/content/description/736/
题解
一句话概括:考虑顺序的01背包问题
详细题解:
stone: 花费时间si, 初始能量ei, 每秒损失的能量li
假设最优解的选取序列为x1,x2,...,xk,xk+1,..xn,其中xk+1-xn选取时的能量已经为0了
我们需要保证交换x1-xk中任意两个相邻能量石之后,所能选取的总能量一定会变小
假设i=l, j=l+1 (1<=i<j<=k),选取到i时,已经消耗了t时间
为了满足条件,则有以下推论:
ei-t*li+ej-(t+si)*lj >= ej-t*lj+ei-(t+sj)*li
ei-t*li+ej-t*lj-si*lj >= ej-t*lj+ei-t*li-sj*li
si*lj <= sj*li
将原能量石顺序,按照si*lj <= sj*li进行排列
然后我们需要构建一个选取集合,必定包含最优解的序列
我们可以将最优解序列理解为,在排好序的能量石序列中,按顺序选取k个物品,不选取一些物品(xk+1-xn能量为0可以理解为不选)
最大价值是多少,是典型的01背包问题,选取k个物品,可以通过枚举时间来确定,选k个物品所耗费的时间必定在0-sum(si)之间
f[i][j]:从前i个选,花费时间恰好为j的所有方案中,能量石最大的的一种
设sx1+sx2+sx3+...+sxk=kt,即最优解选取有效能量石所花费的时间
f[n][kt]的方案集合中必定包含最优解
AC代码
import java.util.*;
public class Main {
static int N = 110, M = 10010, inf = -0x3f3f3f3f;
static int[][] f = new int[N][M];
static Stone[] stones = new Stone[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
for (int tt = 1; tt <= T; tt ++) {
Arrays.fill(f[0], inf);
f[0][0] = 0;
int n = sc.nextInt(), m = 0;
for (int i = 1; i <= n; i ++) {
stones[i] = new Stone(sc.nextInt(), sc.nextInt(), sc.nextInt());
m += stones[i].s;
}
Arrays.sort(stones, 1, n + 1, (o1,o2)-> o1.s*o2.l - o2.s*o1.l);
for (int i = 1; i <= n; i ++)
for (int j = 0; j <= m; j ++) {
f[i][j] = f[i - 1][j];
if (j >= stones[i].s)
f[i][j] = Math.max(f[i][j],
f[i - 1][j - stones[i].s] + Math.max(0, stones[i].e - (j - stones[i].s)*stones[i].l));
}
int res = inf;
for (int i = 0; i <= m; i ++) res = Math.max(res, f[n][i]);
System.out.printf("Case #%d: %d\n", tt, res);
}
}
}
class Stone {
int s, e, l;
Stone(int s, int e, int l) {
this.s = s;
this.e = e;
this.l = l;
}
}