1492: [NOI2007]货币兑换Cash
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 5690 Solved: 2289
[ Submit][ Status][ Discuss]
Description
Input
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
Sample Input
1 1 1
1 2 2
2 2 3
Sample Output
HINT
Source
现在才知道斜率优化可以理解为截距最大化, 太弱辣... 以前都是用不等式来推导的, 发现用截距最大化来理解很神...
这道题很明显, 长得就像一个dp. 每天可以多次操作, 但是我们想啊, 如果说要买, 那肯定是买了优才买, 那我还不如全买, 卖也是同理. 那么仔细想想到了第i天的最优策略肯定是在第j天把钱全部换成A和B,然后在这一天全部卖掉. 当然也有可能第i天不买不卖, 那么就延续i-1的状态就好了. 我们设f[i]表示到了第i天的时候我最多能赚多少钱. 那么第i天最多能换多少A多少B呢. 假设B可换y张,那么A就可以换到rate[i] * y张. 那么就得到y * b[i] + rate[i] * y * a[i] = f[i], 然后化一下式子就会发现y = f[i] / (a[i] * r[i] + b[i]), 也就是最多换的B, 同理乘上rate[i]就可以得到A的数量. 那么f[i]的转移方程也就是呼之欲出(r[j]就是rate[j]):
f[i] = max(f[i - 1], ( (f[j] * r[j]) / (a[j] * r[j] + b[j])) * a[i] + (f[j] / (a[j] * r[j] + b[j])) * b[i]) ).
然后先不看f[i - 1], 我们推导一下原式发现令x[j] = (f[j] * r[j]) / (a[j] * r[j] + b[j])) , y[j] = (f[j] / (a[j] * r[j] + b[j])). 那么就是f[i] = x[j] * a[i] + y[j] * b[i], 想办法把所求的变成截距就化成了:
y[j] = - (a[i] / b[i]) * x[j] + f[i] / b[i]. 那么把所有的x[j]想成二维坐标系x坐标, y[j]想成y坐标. 我们会发现取max在这里就是使截距最大化. 每个x[j], y[j]就是一个点. 由于截距要最大, 那么最优j一定是在凸壳上. 我们的-a[i]/b[i]就像当时于是用这个斜率去切这个凸壳. 显然这是一个上凸壳, 截距最大的时候就是斜率穿过某点时所有点均在斜率直线右侧. 此时这个点就是最优点j. 它与凸壳上左右构成的斜率一个大于-a[i]/b[i], 一个小于. 由于凸壳上斜率单调我们二分一下就能知道最优点在哪里. 但是由于每次的x[i]不是单增的, 那么就涉及到在原来凸壳上插入的情况. 那么就用Splay以x[i]为关键字动态维护这个凸壳就可以了. 具体直接可以维护凸壳每个点和左右点构成的斜率lk[x]和rk[x]. 插入一个点的时候分别向这个点x左右删点. 删点的时候, 由于splay里x单增且又维护的是上凸壳, 所以斜率单减. 那么我们靠lk和rk就能分别在左右找到能与当前插入点x构成新的凸包的点, 中间的删去即可——这也就是splay来维护的好处, 直接区间提取一起删除.
发现splay越来越好写了啊 , 1A
#include<bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int maxn = 2e5 + 5;
const double eps = 1e-10;
int n, rt;
int c[maxn][2], fa[maxn];
double lk[maxn], rk[maxn], y[maxn], x[maxn], f[maxn], a[maxn], b[maxn], r[maxn];
inline double getk(int i, int j) {
if (fabs(x[j] - x[i]) < eps) return -inf;
return (y[j] - y[i]) / (x[j] - x[i]);
}
inline void rotate(int x, int &wanna) {
int y = fa[x], z = fa[y];
int l = (c[y][1] == x), r = l ^ 1;
if (y ^ wanna) c[z][c[z][1] == y] = x;
else wanna = x;
fa[x] = z, fa[y] = x, fa[c[x][r]] = y;
c[y][l] = c[x][r], c[x][r] = y;
}
inline void splay(int x, int &wanna) {
for (int f; x ^ wanna; rotate(x, wanna))
if ((f = fa[x]) ^ wanna)
rotate((c[fa[f]][0] == f ^ c[f][0] == x) ? x : f, wanna);
}
void insert(int &k, int nx, int f) {
if (!k) {
k = nx, fa[k] = f;
splay(nx, rt);
return;
}
if (x[nx] <= x[k] + eps) insert(c[k][0], nx, k);
else insert(c[k][1], nx, k);
}
void erase(int x) {
splay(x, rt);
rt = c[x][1], c[rt][0] = c[x][0], fa[rt] = 0;
fa[c[rt][0]] = rt;
lk[rt] = rk[c[rt][0]] = getk(c[rt][0], rt);
}
inline int prev(int x) {
int k = c[x][0], tmp = x;
while (k) {
if (getk(k, x) <= lk[k] + eps) tmp = k, k = c[k][1];
else k = c[k][0];
}
return tmp;
}
inline int succ(int x) {
int k = c[x][1], tmp = x;
while (k) {
if (getk(x, k) + eps >= rk[k]) tmp = k, k = c[k][0];
else k = c[k][1];
}
return tmp;
}
inline void push_in(int x) {
insert(rt, x, 0);
if (c[x][0]) {
int lf = prev(x);
splay(lf, c[x][0]), c[lf][1] = 0;
lk[x] = rk[lf] = getk(lf, x);
}
else lk[x] = inf;
if (c[x][1]) {
int rg = succ(x);
splay(rg, c[x][1]), c[rg][0] = 0;
rk[x] = lk[rg] = getk(x, rg);
}
else rk[x] = -inf;
if (lk[x] <= rk[x] + eps) erase(x);
}
int ffind(int x, double k) {
if (!x) return 0;
if (lk[x] + eps >= k && k + eps >= rk[x]) return x;
else if (lk[x] + eps >= k) return ffind(c[x][1], k);
else ffind(c[x][0], k);
}
int main() {
scanf("%d%lf", &n, &f[0]);
register int i;
for (i = 1; i <= n; ++ i) scanf("%lf%lf%lf", &a[i], &b[i], &r[i]);
for (i = 1; i <= n; ++ i) {
int j = ffind(rt, -a[i] / b[i]);
f[i] = max(f[i - 1], x[j] * a[i] + y[j] * b[i]);
y[i] = f[i] / (a[i] * r[i] + b[i]);
x[i] = r[i] * y[i];
push_in(i);
}
printf("%.3lf\n", f[n]);
}