题目大意
有(n)天,每天都是同样的两只股票。每天都有三个参数(A_i,B_i,R_i),表示当天股票价格,以及买入的(A,B)两股数量之比为(R_i:1)。
提示:
一定存在最优方案使得每天要么不动,要么全部卖出
思路
这道题拖了好久啊!!!主要是以前对斜率优化不是特别理解(尽管可能以后看来现在的我也不是很理解)(大雾
提示似乎已经写得很清楚了,如果没有这个提示的话估计这个贪心都没办法猜到吧。
我们可以设(f_i)为第(i)天卖出交易之后收获的最大( exttt{money}),于是我们可以得到( exttt{dp})转移式:
其中(x_j,y_j)表示的是第(j)天把所有钱换做股票的(A,B)两股的数量。可以得到(x_j=dfrac{f_jR_j}{A_jR_j+B_j},y_j=dfrac{f_j}{A_jR_j+B_j})。
于是,我们就可以得到:
如果我们设一条经过原点,斜率为(-dfrac{A_i}{B_i})的直线为(Q),于是,我们要求的就是用(Q)去切,使截距最大的((x_j,y_j))。(截距就是当(x=x_j)时(y_j)与函数值差的绝对值,不理解见下图)
我们可以发现的是,如果当前点与左边点的斜率比(Q)的斜率小,那把当前点移到左边点,那么答案肯定更优。类似的,如果当前点与右边点的斜率比(Q)的斜率大,那把当前点移到右边点,那么答案肯定更优。大意如下图:
于是,我们就可以得到,我们需要维护的其实是个凸包,最优的答案就在凸包上。(这个自己想一下就可以明白了)这个东西有(2)种维护方法,一种是( exttt{Splay}),另外一种是( exttt{cdq})分治,我采用的前者。
首先很显然的是,( exttt{Splay})内部以(x)坐标为关键字。
很显然,我们查询的答案的话直接按照上面的方法暴力走就好了,因为凸包的斜率单调递减,又因为( exttt{Splay})的深度是(log n)级别的,所以此操作单次是(log n)的。
我们考虑插入,很显然我们需要找到可以构成凸包的最远左右端点。大意如下图:
updated on 2020-07-15:
其实不是最远左右端点,而是最近的可以构成左右端点的点。
很显然,我们直接把中间的点直接删完即可。
找到最远左右端点直接在( exttt{Splay})上查找即可,单次时间复杂度为(log n)。
不过需要注意的是,我们还需要把插入点在凸包内的情况排除掉。不过这道题数据很水,即使不判也不会有什么问题。
具体实现的话,直接( exttt{Splay})里面维护每个凸包上的点在凸包上与左右两端的点的斜率。
这道题也很卡精度,所以尽量不要卡得很严,大于小于的时候多加几个( ext{eps})。
综上,总时间复杂度为(Theta(nlog n))。
话说斜率优化不就是保证(x)坐标单调上升的版本么?(雾
( ext {Code})
#include <bits/stdc++.h>
using namespace std;
#define Int register int
#define MAXN 100005
#define eps 1e-9
#define inf 1e9
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
int n,rt,fa[MAXN],son[MAXN][2];
double A[MAXN],B[MAXN],R[MAXN],X[MAXN],Y[MAXN],lk[MAXN],rk[MAXN],dp[MAXN];
bool rnk (int x){return son[fa[x]][1] == x;}
void rotate (int x,int &root){
int y = fa[x],z = fa[y],k = rnk (x),w = son[x][!k];
if (y == root) root = x;else son[z][rnk (y)] = x;
son[x][!k] = y,son[y][k] = w,fa[w] = y,fa[x] = z,fa[y] = x;
}
void Splay (int x,int &root){
while (x ^ root){
int y = fa[x];
if (y ^ root) rotate (rnk (x) == rnk (y) ? y : x,root);
rotate (x,root);
}
}
int find (int x,double Slope){
if (!x) return 0;
if (lk[x] + eps >= Slope && rk[x] <= Slope + eps) return x;
else if (lk[x] < Slope + eps) return find (son[x][0],Slope);
else return find (son[x][1],Slope);
}
double GetSlp (int a,int b){
if (X[a] - X[b] > -eps && X[a] - X[b] < eps) return -inf;
return (Y[b] - Y[a]) / (X[b] - X[a]);
}
int pre (int x){
int y = son[x][0],ans = y;
while (y){
if (lk[y] + eps >= GetSlp (y,x)) ans = y,y = son[y][1];
else y = son[y][0];
}
return ans;
}
int suf (int x){
int y = son[x][1],ans = y;
while (y){
if (rk[y] <= GetSlp (x,y) + eps) ans = y,y = son[y][0];
else y = son[y][1];
}
return ans;
}
void ins (int &x,int las,int id){
if (!x) return x = id,fa[x] = las,void ();
if (X[id] <= X[x] + eps) ins (son[x][0],x,id);
else ins (son[x][1],x,id);
}
void Rebuild (int x){
Splay (x,rt);
if (son[x][0]){
int k = pre (x);
Splay (k,son[x][0]),son[k][1] = 0,rk[k] = lk[x] = GetSlp (k,x);
}
else lk[x] = inf;
if (son[x][1]){
int k = suf (x);
Splay (k,son[x][1]),son[k][0] = 0,lk[k] = rk[x] = GetSlp (x,k);
}
else rk[x] = -inf;
if (lk[x] <= rk[x] + eps){
rt = son[x][0],son[rt][1] = son[x][1],fa[son[rt][1]] = rt,fa[rt] = 0;
rk[rt] = lk[son[rt][1]] = GetSlp (rt,son[rt][1]);
}
}
signed main(){
scanf ("%d%lf",&n,&dp[0]);
for (Int i = 1;i <= n;++ i){
scanf ("%lf%lf%lf",&A[i],&B[i],&R[i]);int best = find (rt,-A[i] / B[i]);
dp[i] = max (dp[i - 1],X[best] * A[i] + Y[best] * B[i]);
Y[i] = dp[i] / (A[i] * R[i] + B[i]),X[i] = Y[i] * R[i];
ins (rt,0,i),Rebuild (i);
}
printf ("%.3f
",dp[n]);
return 0;
}
updated on 2020-07-15:
其实上面的代码是有问题。我们在删去凸包里面的点的时候有可能删掉不该删的点,比如:
我们在删除掉( exttt {H})的时候会顺带删掉原凸包上的( exttt{C}),但这显然不合法。但是这道题的数据里面根本就没有点在凸包里面的情况,所以还是草草过掉了。