这题不是很难啊,但问题是卡了好久啊啊啊啊啊——
累了,感觉套路大体上是相同的,就不详细描述了。
发现一个重要性质:每次买进一定买完,卖出一定卖完。
如果买进的券在未来可以让你赚钱,那你一定是买完最优,而卖出同理。
那么显然可以干出来一个 $O(n^2)$ 的 DP,设 $f_i$ 为前 $i$ 天能获得的最大收益。
枚举 $j$ 表示上一次在 $j$ 卖出所有券,这次在 $i$ 全部买回来。
然后把 $\max$ 里面的东西拆成一个一次函数的形式(把和 $i$ 的项提出来)就可以直接李超树维护了。
另外这东西看上去就是一个 $i,j$ 相关的项相乘的形式,推一下式子会发现它两维都不单增,可以 CDQ 或者平衡树维护上凸壳。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 15;
int n, m;
double S, a[N], b[N], r[N], x[N], y[N];
double pos[N], qwq[N];
double Max;
struct Tree {
int l, r;
int id;
} tr[N << 2];
// x->k y->b
double get(int u, int x) {
double xx = qwq[x];
return (::x[u] * xx) + y[u];
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
tr[u].id = 0;
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
void change(int u, int d) {
if (tr[u].l == tr[u].r) {
if (!tr[u].id || get(tr[u].id, tr[u].l) < get(d, tr[u].l)) tr[u].id = d;
return;
}
int l = tr[u].l, r = tr[u].r;
int mid = l + r >> 1;
if (!tr[u].id || get(tr[u].id, mid) < get(d, mid)) swap(tr[u].id, d);
if (get(tr[u].id, l) < get(d, l)) change(u << 1, d);
else if (get(tr[u].id, r) < get(d, r)) change(u << 1 | 1, d);
}
double query(int u, int x) {
if (tr[u].l == tr[u].r) return get(tr[u].id, x);
int mid = tr[u].l + tr[u].r >> 1;
double res = get(tr[u].id, x);
if (x <= mid) return max(res, query(u << 1, x));
else return max(res, query(u << 1 | 1, x));
}
int main() {
scanf("%d%lf", &n, &S);
for (int i = 1; i <= n; i++) scanf("%lf%lf%lf", &a[i], &b[i], &r[i]), pos[i] = a[i] / b[i], qwq[i] = pos[i];
sort(qwq + 1, qwq + 1 + n);
m = unique(qwq + 1, qwq + 1 + n) - qwq - 1;
build(1, 1, m);
Max = S;
for (int i = 1; i <= n; i++) {
int p = lower_bound(qwq + 1, qwq + 1 + m, pos[i]) - qwq;
Max = max(Max, query(1, p) * b[i]);
x[i] = (Max * r[i]) * 1.00 / (a[i] * r[i] + b[i]);
y[i] = Max * 1.00 / (a[i] * r[i] + b[i]);
change(1, i);
}
printf("%.3lf\n", Max);
return 0;
}