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;
}