zoukankan      html  css  js  c++  java
  • AcWing 734 能量石

    题目传送门

    一、贪心(微扰)

    贪心+DP

    集合角度思考:
    求某个集合的最优解,求所有不同的吃法的最优解。这里的不同有两个维度,\(①\)选择吃哪些能量石;\(②\)按什么样的顺序吃。
    本身是两维变化,不好做,哪些,顺序都需要关注,所以从题目中挖掘了一些性质,通过贪心简化,再去动态规划。

    前置知识:

    贪心的微扰(邻项交换)证法,例题:国王游戏耍杂技的牛

    假定当前所选的能量石为\(k\)个,考虑怎样的排列是最优的。

    对于任一排列\(a_1,a_2,a_3,…a_k\)
    考虑任两项\(a_i,a_{i+1}\)
    其交换后,不影响其他能量石的收益。

    假定收集完第\(i−1\)块能量石时是第\(k\)秒,考虑交换前和交换后的收益:

    交换前:
    \(E_i−kL_i+E_{i+1}−(k+S_i)L_{i+1}=E_i+E_{i+1}−(kL_i+(k+S_i)L_{i+1})\)

    交换后:
    \(E_{i+1}−kL_{i+1}+E_i−(k+S_{i+1})L_i=E_i+E_{i+1}−(kL_{i+1}+(k+S_{i+1})L_i)\)

    因此,我们只需要比较
    \(kL_i+(k+S_i)L_{i+1},kL_{i+1}+(k+S_{i+1})L_i\) 的大小

    \(kL_i+(k+S_i)L_{i+1}=k(L_i+L_{i+1})+S_iL_{i+1}\)

    \(kL_{i+1}+(k+S_{i+1})L_i=k(L_i+L_{i+1})+S_{i+1}L_i\)

    因此要比较的即
    \(S_iL_{i+1},S_{i+1}L_i\)

    交换前更优,即
    \(S_iL_{i+1}≤S_{i+1}L_i\)

    交换后更优,即:
    \(S_iL_{i+1}≥S_{i+1}L_i\)

    因此直接按\(S_iL_{i+1},S_{i+1}L_i\)排序即可。

    因此,对于任一种组合我们是能唯一确定一种最大的排列的。

    因此,直接在排序后做\(01\)背包即可。

    二、01背包

    背包问题有三种形态:至多/恰好/至少,本题是哪种呢?
    因为在不同时刻吃所能吸收的能量是不同的,而且每块能量石从最开始就在损失能量。所以能量的价值和时间是相关的,如果剩余空间长大,未必就一定划算,这就说明剩余空间需要一个精确值,而不能表示一个范围,所以是恰好

    • 占用的空间:\(q[i].s\)

    • 获得的价值:\(max(0, q[i].e - (j - q[i].s) * q[i].l)\) (\(j\)为当前花费时长)。

    \(f[j]\) 表示当前恰好\(j\)时间得到的最大能量。

    • 状态转移方程:

    \(f[j] = max(f[j], f[j - q[i].s] + max(0, q[i].e - (j - q[i].s) * q[i].l))\)

    由于我们背包放物品(宝石)的顺序是坐标从\(1\)\(n\)的,所以一定能枚举到最优解。

    初始状态:\(f[0]=0\),其余为负无穷(因为是求最大值)

    答案:\(max(f[i])\)

    二、二维朴素版本

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 110;
    const int M = 10010;
    
    int n;
    struct Node {
        int s;           //吃掉这块能量石需要花费的时间为s秒
        int e;           //可以获利e个能量
        int l;           //不吃的话,每秒失去l个能量
    } q[N];              //能量石的数组,其实这里开的数组开的特别大了,题目中说是110就够了。
    
    //结构体对比函数
    bool cmp(const Node &a, const Node &b) {
        return a.s * b.l < b.s * a.l;
    }
    
    int f[N][M];
    int idx;        //输出是第几轮的测试数据
    int main() {
        int T;
        cin >> T;
        while (T--) {
            cin >> n;
            int m = 0;//总时长
            for (int i = 1; i <= n; i++) {
                cin >> q[i].s >> q[i].e >> q[i].l;
                m += q[i].s;
            }
            //排序
            sort(q + 1, q + 1 + n, cmp);
    
            //二维01背包
            for (int i = 1; i <= n; i++)
                for (int j = 0; j <= m; j++) {//枚举每一个可能的总时长
                    //如果不要物品i
                    f[i][j] = f[i - 1][j];
                    //如果可以要物品i
                    if (j >= q[i].s) {
                        /*
                         q[i].e                 i物品带来的能量
                         j-q[i].s               在i物品讨论之前用的时长
                         q[i].l*(j-q[i].s)      在要物品i之前i物品已经消耗掉的能量
                         */
                        int v = q[i].e - q[i].l * (j - q[i].s);
                        //如果要了物品i会带来价值上的提升
                        if (v) f[i][j] = max(f[i][j], f[i - 1][j - q[i].s] + v);
                    }
                }
            //恰好,需要遍历每一个可能的体积,找出最后的最优解
            int res = 0;
            for (int i = 0; i <= m; i++) res = max(res, f[n][i]);
            printf("Case #%d: %d\n", ++idx, res);
        }
        return 0;
    }
    

    三、一维简化版本

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 110;   //能量石个数上限
    const int M = 10010; //能量上限
    
    //用来描述每个能量石的结构体
    struct Node {
        int s;           //吃掉这块能量石需要花费的时间为s秒
        int e;           //可以获利e个能量
        int l;           //不吃的话,每秒失去l个能量
    } q[N];              //能量石的数组,其实这里开的数组开的特别大了,题目中说是110就够了。
    
    //结构体对比函数
    bool cmp(const Node &a, const Node &b) {
        return a.s * b.l < b.s * a.l;
    }
    
    int n;               //能量石的数量
    int f[M];            //f[i]:花i个时间得到的最大能量
    int idx;             //输出是第几轮的测试数据
    
    int main() {
        //T组测试数据
        int T;
        cin >> T;
        while (T--) {
            //初始化为负无穷,预求最大,先设最小
            memset(f, -0x3f, sizeof f);
            cin >> n;
            //总时长,背包容量
            int m = 0;
            //读入数据
            for (int i = 1; i <= n; i++) {
                cin >> q[i].s >> q[i].e >> q[i].l;
                m += q[i].s;
            }
            //贪心排序
            sort(q + 1, q + 1 + n, cmp);
    
            //01背包,注意恰好装满时的状态转移方程的写法
            //不能是至多j,而是恰好j
            //这是因为如果时间越长,不见得获取的能量越多,因为能量石会损耗掉
            //恰好的,最终需要在所有可能的位置去遍历一次找出最大值
            f[0] = 0;
            for (int i = 1; i <= n; i++)
                for (int j = m; j >= q[i].s; j--) {
                    int v = q[i].e - (j - q[i].s) * q[i].l;
                    if (v) f[j] = max(f[j], f[j - q[i].s] + v);
                }
    
            //恰好装满,需要遍历一次dp数组找出最大值
            int res = 0;
            for (int i = 1; i <= m; i++) res = max(res, f[i]);
            printf("Case #%d: %d\n", ++idx, res);
        }
        return 0;
    }
    
  • 相关阅读:
    Linux目录结构详解
    Linux快捷键列表
    正则表达式
    Python内置函数7
    Python内置函数6
    Python内置函数5
    什么才是java的基础知识?
    单点登录原理与简单实现
    window系统 查看端口 被哪个进程占用了
    Linux Tomcat日志查看实用命令
  • 原文地址:https://www.cnblogs.com/littlehb/p/15728094.html
Copyright © 2011-2022 走看看