【BZOJ1492】【NOI2007】貨幣兌換(動態規劃,CDQ分治,Splay)
題面
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
Output
只有一個實數MaxProfit,表示第N天的操作結束時能夠獲得的最大的金錢數目。答案保留3位小數。
Sample Input
3 100
1 1 1
1 2 2
2 2 3
Sample Output
225.000
HINT
題解
首先考慮一下暴力吧
我們想想,肯定是一天把手上所有錢全部買了之後在後面的某一天把他們全部賣出去。
考慮一個
設
當然,記錄手中的最大錢數或者
那麼,
對於一個
這樣的
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define ll long long
#define RG register
#define MAX 111111
double A[MAX],B[MAX],R[MAX];
double f[MAX],ans;
int n,S;
int main()
{
ios::sync_with_stdio(false);
cin>>n>>S;
for(int i=1;i<=n;++i)cin>>A[i]>>B[i]>>R[i];
f[1]=S*R[1]/(A[1]*R[1]+B[1]);
ans=S;
for(int i=2;i<=n;++i)
{
for(int j=1;j<i;++j)
ans=max(ans,f[j]*A[i]+f[j]/R[j]*B[i]);
f[i]=ans*R[i]/(A[i]*R[i]+B[i]);
}
printf("%.3lf\n",ans);
return 0;
}
顯然在
現在考慮的是快速計算
把關於
假設
化簡得到
上面那坨東西好不爽
令
左邊這個像個斜率的形式,右邊像直線的兩點式
因此,對於每一天
都可以對應一個點
以及一個斜率
維護一個凸殼??
但是
沒法用單調棧來維護。。。很蛋疼啊。。。
要求的東西顯然是所有的直線中,斜率小於目標
並且連線之後與
所以,維護一個上凸殼,在裡面維護斜率,
同時,支援任意插點,動態維護凸殼,二分斜率等操作
看起來是個平衡樹之類的東西。。。
方法一:
我肯定不會閒著蛋疼先去寫平衡樹,
所以我們先來寫一個稍微好寫點的
可以參考
其實我也是照著網上題解打的
這裡稍微換一下,
其他的轉移大致相同,不再提了。
當然,座標也有一點點小小的變化
考慮如何
很顯然,如果我要求出
顯然就要維護出
所以進行
第一點,肯定是按照天數排序分割左右
但是同一側內部不一定按照天數
考慮怎麼計算左右之間的貢獻
相當把左側的凸殼先構造出來,右側按照斜率排序
用一個單調指標掃過去依次更新答案就行了
因為左側要維護凸殼
因此歸併排序的時候按照
每次暴力把左側的凸殼構造出來,複雜度
當只有一個點的時候,這個點的
同時,