Live2D

题解 货币兑换

题目传送门

题目大意

\(n\)天,每天都是同样的两只股票。每天都有三个参数\(A_i,B_i,R_i\),表示当天股票价格,以及买入的\(A,B\)两股数量之比为\(R_i:1\)

提示:

一定存在最优方案使得每天要么不动,要么全部卖出

思路

这道题拖了好久啊!!!主要是以前对斜率优化不是特别理解(尽管可能以后看来现在的我也不是很理解)(大雾

提示似乎已经写得很清楚了,如果没有这个提示的话估计这个贪心都没办法猜到吧。

我们可以设\(f_i\)为第\(i\)天卖出交易之后收获的最大\(\texttt{money}\),于是我们可以得到\(\texttt{dp}\)转移式:

\[f_i=\max\{x_j A_i+y_jB_i\} \]

其中\(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}\)

于是,我们就可以得到:

\[y_j=-\frac{A_i}{B_i}x_j+\frac{f_i}{B_i} \]

如果我们设一条经过原点,斜率为\(-\dfrac{A_i}{B_i}\)的直线为\(Q\),于是,我们要求的就是用\(Q\)去切,使截距最大的\((x_j,y_j)\)。(截距就是当\(x=x_j\)\(y_j\)与函数值差的绝对值,不理解见下图)

我们可以发现的是,如果当前点与左边点的斜率比\(Q\)的斜率小,那把当前点移到左边点,那么答案肯定更优。类似的,如果当前点与右边点的斜率比\(Q\)的斜率大,那把当前点移到右边点,那么答案肯定更优。大意如下图:

于是,我们就可以得到,我们需要维护的其实是个凸包,最优的答案就在凸包上。(这个自己想一下就可以明白了)这个东西有\(2\)种维护方法,一种是\(\texttt{Splay}\),另外一种是\(\texttt{cdq}\)分治,我采用的前者。

首先很显然的是,\(\texttt{Splay}\)内部以\(x\)坐标为关键字。

很显然,我们查询的答案的话直接按照上面的方法暴力走就好了,因为凸包的斜率单调递减,又因为\(\texttt{Splay}\)的深度是\(\log n\)级别的,所以此操作单次是\(\log n\)的。

我们考虑插入,很显然我们需要找到可以构成凸包的最远左右端点。大意如下图:

updated on 2020-07-15:

其实不是最远左右端点,而是最近的可以构成左右端点的点。

很显然,我们直接把中间的点直接删完即可。

找到最远左右端点直接在\(\texttt{Splay}\)上查找即可,单次时间复杂度为\(\log n\)

不过需要注意的是,我们还需要把插入点在凸包内的情况排除掉。不过这道题数据很水,即使不判也不会有什么问题。

具体实现的话,直接\(\texttt{Splay}\)里面维护每个凸包上的点在凸包上与左右两端的点的斜率。

这道题也很卡精度,所以尽量不要卡得很严,大于小于的时候多加几个\(\text{eps}\)

综上,总时间复杂度为\(\Theta(n\log n)\)

话说斜率优化不就是保证\(x\)坐标单调上升的版本么?(雾

\(\text {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\n",dp[n]);
	return 0;
}

updated on 2020-07-15:

其实上面的代码是有问题。我们在删去凸包里面的点的时候有可能删掉不该删的点,比如:

我们在删除掉\(\texttt {H}\)的时候会顺带删掉原凸包上的\(\texttt{C}\),但这显然不合法。但是这道题的数据里面根本就没有点在凸包里面的情况,所以还是草草过掉了。

posted @ 2020-07-14 21:51  Dark_Romance  阅读(194)  评论(0编辑  收藏  举报