[BZOJ]1492 貨幣兌換Cash 斜率優化 動態維護凸包

1492: [NOI2007]貨幣兌換Cash

Time Limit: 5 Sec  Memory Limit: 64 MB
Submit: 5690  Solved: 2289
[Submit][Status][Discuss]

Description

小Y最近在一家金券交易所工作。該金券交易所只發行交易兩種金券:A紀念券(以下簡稱A券)和 B紀念券(以下
簡稱B券)。每個持有金券的顧客都有一個自己的帳戶。金券的數目可以是一個實數。每天隨着市場的起伏波動,
兩種金券都有自己當時的價值,即每一單位金券當天可以兌換的人民幣數目。我們記錄第 K 天中 A券 和 B券 的
價值分別爲 AK 和 BK(元/單位金券)。爲了方便顧客,金券交易所提供了一種非常方便的交易方式:比例交易法
。比例交易法分爲兩個方面:(a)賣出金券:顧客提供一個 [0,100] 內的實數 OP 作爲賣出比例,其意義爲:將
 OP% 的 A券和 OP% 的 B券 以當時的價值兌換爲人民幣;(b)買入金券:顧客支付 IP 元人民幣,交易所將會兌
換給用戶總價值爲 IP 的金券,並且,滿足提供給顧客的A券和B券的比例在第 K 天恰好爲 RateK;例如,假定接
下來 3 天內的 Ak、Bk、RateK 的變化分別爲:
假定在第一天時,用戶手中有 100元 人民幣但是沒有任何金券。用戶可以執行以下的操作:
注意到,同一天內可以進行多次操作。小Y是一個很有經濟頭腦的員工,通過較長時間的運作和行情測算,他已經
知道了未來N天內的A券和B券的價值以及Rate。他還希望能夠計算出來,如果開始時擁有S元錢,那麼N天后最多能
夠獲得多少元錢。

Input

輸入第一行兩個正整數N、S,分別表示小Y能預知的天數以及初始時擁有的錢數。接下來N行,第K行三個實數AK、B
K、RateK,意義如題目中所述。對於100%的測試數據,滿足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤1
0^9。
【提示】
1.輸入文件可能很大,請採用快速的讀入方式。
2.必然存在一種最優的買賣方案滿足:
每次買進操作使用完所有的人民幣;
每次賣出操作賣出所有的金券。

Output

只有一個實數MaxProfit,表示第N天的操作結束時能夠獲得的最大的金錢數目。答案保留3位小數。

Sample Input

3 100
1 1 1
1 2 2
2 2 3

Sample Output

225.000

HINT

Source

[Submit][Status][Discuss]


HOME Back

  現在才知道斜率優化可以理解爲截距最大化, 太弱辣... 以前都是用不等式來推導的, 發現用截距最大化來理解很神...

  這道題很明顯, 長得就像一個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]);
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章