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