洛谷 P1417 烹调方案 【背包+推公式】

原题链接

https://www.luogu.com.cn/problem/P1417

题目背景

由于你的帮助,火星只遭受了最小的损失。但 gw 懒得重建家园了,就造了一艘飞船飞向遥远的 earth 星。不过飞船飞到一半,gw 发现了一个很严重的问题:肚子饿了 ~。

gw 还是会做饭的,于是拿出了储藏的食物准备填饱肚子。gw 希望能在 T 时间内做出最美味的食物,但是这些食物美味程度的计算方式比较奇葩,于是绝望的 gw 只好求助于你了。

题目描述

一共有 n 件食材,每件食材有三个属性,ai​,bi​ 和 ci​,如果在 t 时刻完成第 i 样食材则得到 ai​−t×bi​ 的美味指数,用第 i 件食材做饭要花去 ci​ 的时间。

众所周知,gw 的厨艺不怎么样,所以他需要你设计烹调方案使得美味指数最大。

输入格式

第一行是两个正整数 T 和 n,表示到达地球所需时间和食材个数。

  • 下面一行 n 个整数,ai​;
  • 下面一行 n 个整数,bi​;
  • 下面一行 n 个整数,ci​。

输出格式

输出最大美味指数。

输入输出样例

输入 #1

74 1
502
2
47

输出 #1

408

说明/提示

数据范围及约定

  • 对于 40%40% 的数据 1≤n≤10;
  • 对于 100%100% 的数据 1≤n≤50。

所有数字均小于 1e5。

题目来源

tinylic 改编

解题思路

首先根据题目描述的每个食材的美味指数的计算方式可以知道总的美味指数和俩个因素有关

(1)因素一:和选择哪些食材有关

(2)因素二:和处理食材的顺序有关

首先考虑因素1,每个食材可以选或者不选,这显然就是01背包。

然后考虑因素2,食材选定之后,考虑处理食材的顺序让总收益最大。

顺序处理过程推导【推公式】

对于任意俩个食材对(i,j),他们处理的相对顺序有俩种,分别为(i,j),(j,i)

  • 第一种处理顺序为(i,j)时,我们的收益为ai-bi*ci+aj-bj*(ci+cj) 
  • 第二种处理顺序为(j,i)时,我们的收益为aj-bj*cj+ai-bi*(ci+cj)

对这俩个式子化简,第一种处理>第二种处理的条件是bi*cj>bj*ci

我们发现任意满足这个条件的食材对(i,j),i在j之前会使得收益更大

所以食材的顺序可以按照这个条件排序,所以我们可以先对所有食材对进行排序,然后利用刚好装满的01背包处理即可

刚好装满的01背包处理过程 【背包】

状态定义:定义f[i][j]处理表示前i个物品并且处理的时间刚好为j的最大收益

处理的过程中如果有空闲时间不处理食材,那么肯定会让收益变小,所以我们应该抓住所有时间处理,中间不能空闲时间,也就是说所有物品处理都是连续的,所以我们把状态定义为时间刚好为j更好处理状态转移。

初始状态:

memset(f,-0x3f,sizeof f)

f[0][0]=0  (处理完前0个物品,并且时间刚好为0的最大收益为0)

由于处理0个物品时花费的时间只能刚好是0,所以除了f[0][0]之外的所有的f[0][i]都应该初始化为负无穷,表示这些状态是不存在的

状态转移

不处理当前物品

f[i][j]=f[i-1][j]

处理当前物品

f[i][j]=max(f[i][j],f[i-1][j-nums[i].c]+nums[i].b*j);

根据上述状态转移可以发现f[i][]只和f[i-1][]有关,所以实际上可以把空间压缩到一维,就是01背包压缩空间的那个写法,但是这个题目空间足够,不压缩也可以过,我这里就不压缩了,要压缩随便改一下就行了,二维写法好理解一点,我这里就写的二维的解法了。

最终答案

处理完前n个物品获得最大收益的花费时间可能刚好为0-T中的任意值

所以答案应该是所有的f[n][i]取最大值 (0<=i<=T)

cpp代码如下

#include 
#include 
#include 
#include 

using namespace std;
typedef long long LL;
const int N = 55, M = 1e5 + 10;

int T, n, m;
struct Node
{
    int a, b, c;
} nums[N];
LL f[N][M];
int main()
{
    cin >> T >> n;
    for (int i = 1; i <= n; i++)
        scanf("%d", &nums[i].a);
    for (int i = 1; i <= n; i++)
        scanf("%d", &nums[i].b);
    for (int i = 1; i <= n; i++)
        scanf("%d", &nums[i].c);
    //根据推导的公式排序
    sort(nums + 1, nums + 1 + n, [&](Node A, Node B)
         { return (LL)A.b * B.c > (LL)B.b * A.c; });
    LL ans = 0;
    memset(f, -0x3f, sizeof f);  //将不存在的状态初始化为负无穷
    f[0][0] = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= T; j++)
        {   
            //不处理第i个物品
            f[i][j] = f[i - 1][j];
            //处理第i个物品,时间刚好为j,也就是处理完第i个物品的时间也是刚好为j,所以第i个物品的收益是nums[i].a-j*nums[i].b
            if (j >= nums[i].c)
                f[i][j] = max(f[i][j], f[i - 1][j - nums[i].c] + nums[i].a - (LL)j * nums[i].b);
        }
    //计算答案
    for (int j = 0; j <= T; j++)
        ans = max(ans, f[n][j]);
    cout << ans << endl;
    return 0;
}

你可能感兴趣的:(动态规划,算法,数据结构)