洛谷多维DP(4):P1417 烹调方案——泛化背包,贪心+DP

P1417 烹调方案

在这里插入图片描述

输入输出样例
输入 #1复制
74 1
502
2
47
输出 #1复制
408

总结目录

1.什么是泛化物品
2.状态方程的书写,排序与DP的原因

1 什么是泛化物品

参考 https://www.kancloud.cn/kancloud/pack/70132
泛化物品是指,物品的价值会随着物品的cost而变化。假设cost为一个变量,那么value=value(cost)。在过去的背包问题中,01背包,完全背包,多重背包物品的价值都是不会改变的。而在分组背包中,先后遍历了组,cost,组内物品,其本质是将节点进行了压缩,取相同cost下的最大价值(类似于01背包中说的“一个简单的优化”)。因此,泛化物品的主要要点有2个,一个就是val是cost的函数,而根据题目得到这个函数需要进行状态压缩。

在这里插入图片描述
在01背包中,物品的价值只有在恰好为cost的时候有价值,即 val(cost) != 0,而 其余的时候 val == 0. 在附件背包中,先通过01背包对同一组内的状态进行压缩,将多种方案泛化为一个物品,然后再在总体计算的时候使用分组背包进行计算。

特别的,我们可以计算两个泛化物品合并为一个泛化物品,计算的方法如上所示,只需枚举给两个物品的资源,然后进行筛选最优(即状态压缩即可)

for v==0: V //总的资源为V
	for k==0:V //分配给物品1的cost为 k
		 l==v-k //分配给物品2的cost为 l == v-k
		 max{ 物品1的价值+物品2的价值| v==不同的总价值 }

2 状态方程的书写,排序与DP的原因

在本题中,再次体现了传纸条一题中的DP的核心:当前状态要正确,则要保证2点,一个是之前的状态必须是正确的,另外一个是状态的转移是正确的。

在这一题中,单纯用01背包来做是会出错的,因为状态改变了,无法从前面的状态得到后面的状态。举例来说就是,假设dp[i][j]定义为前i种物品,恰好花j的时间时候的最大的美味度,那么一般的01背包会有

dp[i][j]=max {dp[i-1][j], dp[i-1][j-list[i].cost]+ list[i].val};

但是注意到,在求 dp[i-1][j-list[i].cost]的时候,不同的i的遍历顺序是会导致结果并非最优化。什么意思?就是我们定义dp[i][j]是前i种材料在恰好花费j的时候的最大的美味度。但是我们同样是i种材料,同样是花费j的时间,但是不同的i的遍历顺序缺会得到不同的dp[i][j]的值,这说明我们的定义不完全,或者说我们的计算方法出现了问题(即不能按照我们随意的顺序i去进行遍历,我们需要按照特定的顺序去遍历才是可行的!)

因此,我们才想到了要用贪心来做,因此才有了贪心的证明,从而有了先排序,再求。这样的话就保证了在合理的遍历顺序下,我们通过01背包计算得到的dp[i][j]的值它确实是i种材料恰好花费j的时候的最大美味度。而不是说这个值是我们算出来的,但不是真实的最大值(或者说是在我们给定的遍历顺序下的,i种材料恰好花费j时间时候的最大美味度)

在这里插入图片描述
在这里插入图片描述

其他的猜想

因此这个背包问题是一个泛化物品的问题。我一开始曾经想过先考虑物件1,物件2,将他们合并求出一个泛化的价值函数,再用12合并的泛化价值函数去和物件3合并。这样子的话在讨论的时候有三种情况

dp=max{ 物品1、2都选,且物品1在物品2前;不选物品2;物品1、2都选,且物品1在物品2之后 }
//使用以下数据结构来记录
struct obj{
int val;
int cost;
}arr[100000];

arr[k]表示在k时刻结束,从k-arr[k].cost时刻开始,经过arr[k].cost时间后,在k时刻得到的最大价值arr[k].val
这样子的话具体计算是由可行性的,但是后来我否决了这个想法。因为,这样计算实际上是只能排前面,排后面,或者不选,但是每次加入后面的物品的时候,我们并不能确定它一定是排在前面或者后面,它可能插在中间,而这种定义方法是没有体现的。

因此,注意到这里的顺序会影响结果,因此我们考虑是否可以利用贪心进行解决该问题。毕竟如果结合了贪心,然后进行排序,再使用01背包,肯定就是一个最好的值了。以下为其他人的证明,它的含义我觉得有误,我认为应该解释为:
在任意时刻p时刻,对于任意两个物品x,y(不一定是相邻的,当然背包里面判断过程中总是选相邻的来判断),那么有

x先执行,y后执行的价值 > x后执行,y先执行的价值
在x.c*y.b<x.b*y.c的条件下满足

有了这个式子之后,我们先排序,这样前面执行的永远会使得价值最大,然后进行01背包即可。
在这里插入图片描述

代码

#include<iostream>
#include<climits>
#include<algorithm>
#include<cstring>
#define LL long long
#define maxsize 100000
using namespace std;
struct obj {
	LL a, b, c;
}list[55];

LL dp[100005] = {};//定义dp[i][j]为考虑前i种食材,经过j时刻后的最大美味度

bool cmp(obj p, obj q) {
	return (LL)p.b*(LL)q.c > (LL)p.c*(LL)q.b;//满足该方程返回true,不换顺序
}

int main() {
	int T,n;
	cin >>T>>n;

	for (int i = 1; i <= n; i++) {
		cin >> list[i].a ;
	}
	for (int i = 1; i <= n; i++) {
		cin >> list[i].b;
	}
	for (int i = 1; i <= n; i++) {
		cin >> list[i].c;
	}

	sort(list+1, list +n+1, cmp);
	memset(dp, -1, sizeof(dp));
	dp[0] = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = T; j >=0; j--) {
			if (j >= list[i].c&&dp[j - list[i].c] != -1) {
				dp[j] = max(dp[j], dp[j - list[i].c] + list[i].a - j*list[i].b);
			}
		}
	}
	LL res = 0;
	for (int i = 0; i <= T; i++) {
		res = max(res, dp[i]);
	}
	cout << res;
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值