C++---背包模型/贪心---能量石(每日一道算法2023.4.10)

注意事项:
本题是"动态规划—01背包"“贪心—耍杂技的牛” 的扩展题,建议先阅读这两篇文章并理解。

题目:
岩石怪物杜达生活在魔法森林中,他在午餐时收集了 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

样例解释
在样例#1中,有 N=4 个宝石。杜达可以选择的一个吃石头顺序是:
吃第四块石头。这需要 5 秒,并给他 80 单位的能量。
吃第二块石头。这需要 5 秒,并给他 5 单位的能量(第二块石头开始时具有 30 单位能量,5 秒后失去了 25 单位的能量)。
吃第三块石头。这需要 100 秒,并给他 20 单位的能量(第三块石头开始时具有 30 单位能量,10 秒后失去了 10 单位的能量)。
吃第一块石头。这需要 20 秒,并给他 0 单位的能量(第一块石头以 10 单位能量开始,110 秒后已经失去了所有的能量)。
他一共获得了 105 单位的能量,这是能获得的最大值,所以答案是 105。

在样本案例#2中,有 N=3 个宝石。
无论杜达选择吃哪块石头,剩下的两个石头的能量都会耗光。
所以他应该吃第三块石头,给他提供 8 单位的能量。

在样本案例#3中,有 N=2 个宝石。杜达可以:
吃第一块石头。这需要 12 秒,并给他 300 单位的能量。
吃第二块石头。这需要 5 秒,并给他 200 单位的能量(第二块石头随着时间的推移不会失去任何能量!)。所以答案是 500。

输入:
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
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 10010;
int n, m, s, e, l, C;
int f[N];
struct Stone {
    
    
    int s, e, l;
    bool operator<(const Stone &other) const {
    
    return s*other.l < other.s*l;}
} Stone[N];

int main() {
    
    
    cin >> C;
    for (int t = 1; t<=C; t++) {
    
        //处理C组样例
        cin >> n;   //本组样例个数
        m = 0;      //本组样例的总时间
        for (int i = 1; i<=n; i++) {
    
        //读入本组的能量石
            cin >> s >> e >> l;
            Stone[i] = {
    
    s, e, l};
            m += s;
        }
        sort(Stone+1, Stone+n+1);   //贪心,切记+1,因为是从下标1开始读入的不是0
        
        //恰好为j的话初始化除了f[0]都为负无穷即可
        memset(f, -0x3f3f, sizeof f);
        f[0] = 0;
        //一维双循环01背包
        for (int i = 1; i<=n; i++) {
    
    
            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", t, res);
    }
    return 0;
}

贪心思路:
"贪心—耍杂技的牛" 很类似,需要先发现题目中的性质转换为公式来进行贪心,
用相同的思路,判断吃第i个能量石和吃i+1个能力石之间的关系。

1.先吃i再吃i+1: E[i]+E[i+1] - s[i]*L[i+1]
2.先吃i+1再吃i: E[i]+E[i+1] - s[i+1]*L[i]
(前半部分相同,后半部分是计算在吃第一个石头的时候第二个石头损失的能量)

那什么情况下1会更好?也就是当1损失的能量比2损失的能量小的时候,就吃到了更多的能量。也就是满足:

s[i]*L[i+1] < s[i+1]*L[i]

显而易见,只要按照这个条件排序,那么就一定能得到先吃i再吃i+1为最优解。

DP思路:
还是经典的y式dp法。

1.状态表示
f[i][j]: 对于前i个物品,体积恰好为j时的所有方案,属性为Max。
(这里用“恰好为j”而不是“不大于j”是因为状态转移时依赖的“j”必须是一个确切的时间点,而不能是一个时间段,如果用不大于j的话,那状态转移时到底是从j-1转移来的?还是从j-2…j-m转移来的?计算就出问题了)

2.状态计算
状态计算就和01背包基本一致了,只不过稍稍改变一下参数,
还是以 选择i/不选择i 来进行划分:
不选i:f[i][j] = f[i-1][j]
选择i:f[i][j] = f[i-1][j-si] + ei-(j-si)*Li
这里 (j-s)*Li 是在计算损失的能量。

如果有所帮助请给个免费的赞吧~有人看才是支撑我写下去的动力!

声明:
算法思路来源为y总,详细请见https://www.acwing.com/
本文仅用作学习记录和交流

猜你喜欢

转载自blog.csdn.net/SRestia/article/details/130063827