P6893题解
Link
P6893 [ICPC2014 WF]Buffed Buffet
Solution
显然这个问题看着就非常背包。
注意到食物其实分成两类,贡献分别是离散和连续的。
回想一下我们初学DP时,有个非常重要的结论:01背包不可以贪心,但是分数背包可以。
那么作用到这题上,我们分开考虑这两种物品。
对于不连续的,我们设计一个背包;对于连续的,我们设计一个贪心。
先考虑相对复杂一点的背包:
设
直接转移要枚举
这个背包虽然是两维的,但是熟悉背包的你肯定知道,
状态上是不太能压缩了。考虑优化转移的过程,利用一下决策单调性,先把式子拆一下:
然后你发现这个东西长得非常的斜率优化,于是我们往这个方面考虑:
我们把下标关于
这个式子就可以斜率优化做了,复杂度
然后考虑连续的贪心部分。
我们每次都取当前美味值最大的,然后,当两种食品的美味值相等的时候,我们“合并”这两种食品。
具体的,当两种美味值分别为
最后枚举下多少给离散,多少给连续,就做完了。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <deque>
#define _D 260
#define _W 10010
#define _INF 1e18
#define _EPS 1e-10
struct C {
int t, dt;
};
struct D {
int w, t, dt;
};
bool inline operator <(const C& left, const C& right) {
return left.t > right.t;
}
int d, w;
int ccnt, dcnt;
C fdc[_D];
D fdd[_D];
namespace workD {
int curt, curdt, curw, r;
double f[_W], tmp[_W];
std::deque<int> q;
double inline x(int p) {
return 1.0 * p;
}
double inline y(int p) {
return tmp[p * curw + r] - 1.0 * p * curt - 0.5 * curdt * p * (p + 1);
}
double inline slope(int left, int right) {
double lx = x(left), rx = x(right), ly = y(left), ry = y(right);
return (ry - ly) / (rx - lx == 0 ? _EPS : rx - lx);
}
void ins(int p) {
while (q.size() > 1 && slope(q[q.size() - 2], q.back()) < slope(q[q.size() - 2], p)) {
q.pop_back();
}
q.push_back(p);
}
void del(double k) {
while (q.size() > 1 && slope(q.front(), q[1]) > k) {
q.pop_front();
}
}
void add(int wi, int t, int dt) {
curw = wi, curt = t, curdt = dt;
for (int i = 1; i <= w; i++) {
tmp[i] = f[i];
f[i] = -_INF;
}
for (r = 0; r < wi; r++) {
q.clear();
for (int j = 0; j * wi + r <= w; j++) {
ins(j);
del(-1.0 * dt * j);
int i = q.front();
f[j * wi + r] = std::max(f[j * wi + r], tmp[i * wi + r] + 1.0 * (j - i) * t - 1.0 * (j - i) * (j - i - 1) / 2 * dt);
}
}
}
void calc() {
for (int i = 1; i <= w; i++) {
f[i] = -_INF;
}
for (int i = 1; i <= dcnt; i++) {
add(fdd[i].w, fdd[i].t, fdd[i].dt);
}
}
}
namespace workC {
double f[_W];
void calc() {
if (ccnt == 0) {
for (int i = 1; i <= w; i++) {
f[i] = -_INF;
}
return;
}
std::sort(fdc + 1, fdc + ccnt + 1);
int pt = 1;
double curv = fdc[1].t, curdt = fdc[1].dt, curw = 0, cursum = 0;
pt++;
for (int i = 1; i <= w; i++) {
while (curw < 1.0 * i) {
if (pt > ccnt || curv - curdt * (i - curw) > 1.0 * fdc[pt].t) {
cursum += curv * (i - curw) - 0.5 * (i - curw) * (i - curw) * curdt;
curv -= curdt * (i - curw), curw = i;
} else {
double q = (curv - 1.0 * fdc[pt].t) / curdt;
cursum += curv * q - 0.5 * q * q * curdt;
curv = fdc[pt].t, curw += q;
curdt = 1.0 / (1.0 / curdt + 1.0 / (fdc[pt].dt != 0 ? fdc[pt].dt : _EPS));
pt++;
}
}
f[i] = cursum;
}
}
}
int main() {
scanf("%d%d", &d, &w);
while (d--) {
char tp[10];
scanf("%s", tp);
switch (tp[0]) {
case 'D':
++dcnt;
scanf("%d%d%d", &fdd[dcnt].w, &fdd[dcnt].t, &fdd[dcnt].dt);
break;
case 'C':
++ccnt;
scanf("%d%d", &fdc[ccnt].t, &fdc[ccnt].dt);
break;
}
}
workC::calc(), workD::calc();
double ans = -_INF;
for (int i = 0; i <= w; i++) {
ans = std::max(ans, workC::f[i] + workD::f[w - i]);
}
if (ccnt == 0 && ans < -1e16) {
puts("impossible");
} else {
printf("%.10lf\n", ans);
}
return 0;
}
Inspiration
我认为这一道题目处理方法整体来说比较自然,没有太过于需要“想象力”的步骤,基本上说是按部就班。
核心结论:
- 背包有一维实际上不重要,故可以看成一维的然后斜率优化
- 两种连续的食品在美味值相同时可以合并