【ACWing】734. 能量石

题目地址:

https://www.acwing.com/problem/content/736/

岩石怪物杜达生活在魔法森林中,他在午餐时收集了 N N N块能量石准备开吃。由于他的嘴很小,所以一次只能吃一块能量石。能量石很硬,吃完需要花不少时间。吃完第 i i i块能量石需要花费的时间为 S i S_i Si秒。杜达靠吃能量石来获取能量。不同的能量石包含的能量可能不同。此外,能量石会随着时间流逝逐渐失去能量。第 i i i块能量石最初包含 E i E_i Ei单位的能量,并且每秒将失去 L i L_i Li单位的能量。当杜达开始吃一块能量石时,他就会立即获得该能量石所含的全部能量(无论实际吃完该石头需要多少时间)。能量石中包含的能量最多降低至 0 0 0。请问杜达通过吃能量石可以获得的最大能量是多少?

输入格式:
第一行包含整数 T T T,表示共有 T T T组测试数据。每组数据第一行包含整数 N N N,表示能量石的数量。接下来 N N N行,每行包含三个整数 S i , E i , L i S_i,E_i,L_i Si,Ei,Li

输出格式:
每组数据输出一个结果,每个结果占一行。结果表示为Case #x: y,其中 x x x是组别编号(从 1 1 1开始), y y y是可以获得的最大能量值。

数据范围:
1 ≤ T ≤ 10 1≤T≤10 1T10
1 ≤ N ≤ 100 1≤N≤100 1N100
1 ≤ S i ≤ 100 1≤S_i≤100 1Si100
1 ≤ E i ≤ 105 1≤E_i≤105 1Ei105
0 ≤ L i ≤ 105 0≤L_i≤105 0Li105

设集合 U U U表示所有这样的吃法,这些吃法的所有能量石都会被吃,而不会由于时间衰减而能量变成 0 0 0(比如在吃第 2 2 2个能量石吃完之后,结果第 5 5 5个能量石由于时间衰减而能量变成 0 0 0了,这种吃法就不会在集合 U U U里。显然 U U U已经包含了所有的吃法了,对于不满足 U U U的条件的吃法,我们只需要去掉那些衰减为 0 0 0的能量石即可。所以以下我们只考虑 U U U里的吃法)。我们任取 U U U的一种吃法,其吃能量石的顺序里,考虑第 i i i个和第 i + 1 i+1 i+1个能量石。设开始吃第 i i i个能量石的时候,第 i i i i + 1 i+1 i+1个能量石的能量是 e i e_i ei e i + 1 e_{i+1} ei+1,那么这两块能量石产生的贡献是 e i + max ⁡ { 0 , e i + 1 − s i l i + 1 } = e i + e i + 1 − s i l i + 1 e_i+\max\{0,e_{i+1}-s_il_{i+1}\}=e_i+e_{i+1}-s_il_{i+1} ei+max{ 0,ei+1sili+1}=ei+ei+1sili+1(因为这种吃法属于 U U U,所以第 i + 1 i+1 i+1个能量石不会衰减成 0 0 0),如果交换这两个能量石的顺序,则它们的贡献是 e i + 1 + max ⁡ { 0 , e i − s i + 1 l i } e_{i+1}+\max\{0,e_i-s_{i+1}l_i\} ei+1+max{ 0,eisi+1li}。我们尝试找出一种贪心顺序,使得按照这种顺序的吃法是最优的。如果交换这两个能量石之后,形成的新吃法仍然属于 U U U,那么就有交换后的贡献 e i + 1 + e i − s i + 1 l i e_{i+1}+e_i-s_{i+1}l_i ei+1+eisi+1li;如果原顺序是最优的,那么就有 e i + e i + 1 − s i l i + 1 ≥ e i + 1 + e i − s i + 1 l i ⇔ s i l i + 1 ≤ s i + 1 l i e_i+e_{i+1}-s_il_{i+1}\ge e_{i+1}+e_i-s_{i+1}l_i\Leftrightarrow s_il_{i+1}\le s_{i+1}l_i ei+ei+1sili+1ei+1+eisi+1lisili+1si+1li,在能量石这个集合上定义一个偏序,能量石 A 1 ≤ A 2 ⇔ l 1 s 1 ≥ l 2 s 2 A_1\le A_2\Leftrightarrow \frac{l_1}{s_1}\ge \frac{l_2}{s_2} A1A2s1l1s2l2(显然它也是个全序关系),我们猜测,按照这种偏序从小到大排序下的吃法是最优的。我们只需证明: s i l i + 1 ≥ s i + 1 l i ⇒ e i + e i + 1 − s i l i + 1 ≤ e i + 1 + max ⁡ { 0 , e i − s i + 1 l i } s_il_{i+1}\ge s_{i+1}l_i\Rightarrow e_i+e_{i+1}-s_il_{i+1}\le e_{i+1}+\max\{0,e_i-s_{i+1}l_i\} sili+1si+1liei+ei+1sili+1ei+1+max{ 0,eisi+1li}因为这意味着对于 U U U里的某种最优吃法,一定可以将其适当做适当调整,使得其变成按上述偏序有序的吃法,并且不会使得结果更差,也就证明了按照上述偏序有序的吃法是最优的。证明如下:
1、如果 max ⁡ { 0 , e i − s i + 1 l i } = e i − s i + 1 l i \max\{0,e_i-s_{i+1}l_i\}=e_i-s_{i+1}l_i max{ 0,eisi+1li}=eisi+1li,这个显然;
2、如果 max ⁡ { 0 , e i − s i + 1 l i } = 0 \max\{0,e_i-s_{i+1}l_i\}=0 max{ 0,eisi+1li}=0,那么相当于要证明 e i + e i + 1 − s i l i + 1 ≤ e i + 1 + max ⁡ { 0 , e i − s i + 1 l i } = e i + 1 e_i+e_{i+1}-s_il_{i+1}\le e_{i+1}+\max\{0,e_i-s_{i+1}l_i\}=e_{i+1} ei+ei+1sili+1ei+1+max{ 0,eisi+1li}=ei+1,即要证 e i ≤ s i l i + 1 e_i\le s_il_{i+1} eisili+1,即 e i − s i + 1 l i ≤ s i l i + 1 − s i + 1 l i e_i-s_{i+1}l_i\le s_il_{i+1}-s_{i+1}l_i eisi+1lisili+1si+1li,而 e i − s i + 1 l i ≤ 0 , s i l i + 1 ≥ s i + 1 l i e_i-s_{i+1}l_i\le 0,s_il_{i+1}\ge s_{i+1}l_i eisi+1li0,sili+1si+1li,所以也成立。
注意,即使做交换后的吃法不在 U U U里了,仍然可以将衰减成 0 0 0的能量石去掉来让新吃法还属于 U U U,并且新吃法的逆序数会变小(或者不变)。

由上面知,我们只需要考虑 U U U里按上述偏序有序的吃法即可。这就可以用动态规划解决了。这是个经典的 0 − 1 0-1 01背包问题。设 f [ i ] [ j ] f[i][j] f[i][j]是只吃前 i i i个石头,总耗时恰好是 j j j的情况下的最大能量,则有 f [ 0 ] [ 0 ] = 0 , f [ 0 ] [ . > 0 ] = − ∞ f[0][0]=0,f[0][.>0]=-\infty f[0][0]=0,f[0][.>0]=,并且: f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − s i ] + max ⁡ { 0 , e i − ( j − s i ) l i } } f[i][j]=\max\{f[i-1][j],f[i-1][j-s_i]+\max\{0,e_i-(j-s_i)l_i\}\} f[i][j]=max{ f[i1][j],f[i1][jsi]+max{ 0,ei(jsi)li}}最后只需返回 max ⁡ j f [ N ] [ j ] \max_j f[N][j] maxjf[N][j]即可。之所以不把状态定义为总耗时恰好是 j j j的情况下的最大能量,是因为递推方程在这种情况下不好写,它并不能写成如上的形式,因为上面的方程实际上是在 j − s i j-s_i jsi的时刻才开始吃第 i i i个石头,但是实际最优方案应该是尽量安排在更早的时候吃(能量损耗少),而这个状态是很难写出转移方程的。所以就将状态定义为恰好总耗时是 j j j,这里的 j j j实际上是吃完最后一个石头的那一刻的时间。代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 10010;
int n;

struct Stone {
    
    
    int s, e, l;
    // 要重新定义石头的比较函数,最好写成乘法,以求精确性,并且避免0作为除数
    bool operator<(const Stone& T) const {
    
    
        return s * T.l < l * T.s;
    }
} stone[N];

// 为了空间优化,可以只开一维数组
int f[N];

int main() {
    
    
    int T;
    cin >> T;
    for (int C = 1; C <= T; C++) {
    
    
        cin >> n;
        // m存总耗时,其范围是从0到si的总和
        int m = 0;
        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, -0x3f, sizeof f);
        f[0] = 0;

        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] + max(0, 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;
}

对于每个case时间复杂度 O ( N ∑ S i ) O(N\sum S_i) O(NSi),空间 O ( ∑ S i ) O(\sum S_i) O(Si)

当然状态依然可以换种定义,使得上面的转移方程仍然正确。可以将 f [ i ] [ j ] f[i][j] f[i][j]定义为只从前 i i i个石头里选,并且所有石头都是在以 j j j结尾的某个连续区间吃完的方案中能量最大值(也就是说这些方案是,可能他先等一会儿时间,然后连续开吃若干个能量石并且中间不暂停,到 j j j时刻恰好吃完)。这样的话以下转移方程确实是对的: f [ i ] [ j ] = max ⁡ { f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − s i ] + max ⁡ { 0 , e i − ( j − s i ) l i } } f[i][j]=\max\{f[i-1][j],f[i-1][j-s_i]+\max\{0,e_i-(j-s_i)l_i\}\} f[i][j]=max{ f[i1][j],f[i1][jsi]+max{ 0,ei(jsi)li}}但是这里要返回答案的时候仍然要返回 max ⁡ j f [ N ] [ j ] \max_j f[N][j] maxjf[N][j],因为最优解应当是他不等待,而是要一上来就开始吃,所以我们依然要遍历 j j j。初始值是 ∀ j , f [ 0 ] [ j ] = 0 \forall j,f[0][j]=0 j,f[0][j]=0。代码如下:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 10010;

int n;
struct Stone {
    
    
    int s, e, l;
    bool operator<(const Stone& T) const {
    
    
        return s * T.l < l * T.s;
    }
} stone[N];
int f[N];

int main() {
    
    
    int T;
    cin >> T;
    for (int C = 1; C <= T; C++) {
    
    
        cin >> n;
        int m = 0;
        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] + max(0, 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;
}

时空复杂度一样。

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/114351108
今日推荐