bzoj千题计划237:bzoj1492: [NOI2007]货币兑换Cash
http://www.lydsy.com/JudgeOnline/problem.php?id=1492
?
dp[i] 表示 第i天賣完的最大收益
樸素的dp:
枚舉從哪一天買來的在第i天賣掉,或者是不操作
dp[i]=max(dp[i-1],X[j]*A[i]+Y[j]*B[i])
其中X[j]表示在第j天能買多少A紀念券,Y[j]表示在第j天能買多少B紀念券
可列方程 X[j]*A[j]+Y[j]*B[j]=dp[j]
又因為 X[j]=Rate[j]*Y[j]
所以解出 Y[j]=dp[j]/(B[j]+A[j]*Rate[j])
?
優化:
dp[i]=X[j]*A[i]+Y[j]*B[i]
Y[j]=dp[i]/B[i] - A[i]/B[i] * x[j]
斜率優化形式,維護上凸殼,最大化截距
點:(X[j],Y[j])
斜率:-A[i]/B[i]
但是 -A[i]/B[i] 不具有單調性
所以不能用單調隊列維護斜率
可以用平衡樹維護,O(n * logn * logn)
更簡單的方法:CDQ分治
?
假設現在正在計算 dp[l]~dp[r],即solve(l,r)
對于每個j∈[l,r],[1,j-1]都是j的一種決策
令 mid=(l+r)/ 2
我們先計算出 dp[l]~dp[mid],然后用這些去更新 dp[mid+1]~dp[r]
假設我們現在已有了dp[l]~dp[mid]的上凸殼
那么如果保證 mid+1~r的斜率單調
就可以在線性時間內完成 dp[l]~dp[mid] 對 dp[mid+1]~dp[r]的更新
斜率單調可以在歸并排序之前的排序預處理中解決
僅剩的問題:如何得到dp[l]~dp[mid] 構成的上凸殼?
在每一層最后歸并的時候,我們用線形的時間使l~mid 的點 有序
即橫坐標為第一關鍵字,縱坐標為第二關鍵字 升序排列,
那么處理完l~mid之后,
會得到l~mid 所有的點 升序排列的結果
對于一些有序的點,用單調棧維護斜率遞減即可維護出上凸殼
然后利用這個上凸殼去更新mid+1~r 即可
復雜度為 O(nlogn)
?
#include<cmath> #include<cstdio> #include<algorithm> #include<iostream>using namespace std;#define N 100001const double eps=1e-7;struct node {double A,B,R;double X,Y;double K;int id;bool operator < (node p) const{/*if(fabs(X-p.X)>eps) return X<p.X;return Y<p.Y;*/return X<p.X;}}e[N],t[N]; double dp[N],ans;int st[N],top;bool down(int i,int j,int k) {return (e[j].X-e[i].X)*(e[k].Y-e[i].Y)-(e[k].X-e[i].X)*(e[j].Y-e[i].Y)<0; }void solve(int l,int r) {if(l==r){dp[l]=max(dp[l],dp[l-1]);e[l].Y=dp[l]/(e[l].A*e[l].R+e[l].B);e[l].X=e[l].Y*e[l].R;return;}int mid=l+r>>1;int opl=l,opr=mid+1;for(int i=l;i<=r;++i)if(e[i].id<=mid) t[opl++]=e[i];else t[opr++]=e[i];for(int i=l;i<=r;++i) e[i]=t[i];solve(l,mid);top=0;for(int i=l;i<=mid;++i){while(top>1 && !down(st[top-1],st[top],i)) top--;st[++top]=i;}int now=1;for(int i=mid+1;i<=r;++i){while(now<top && (e[st[now]].Y-e[st[now+1]].Y)<(e[st[now]].X-e[st[now+1]].X)*e[i].K) now++;dp[e[i].id]=max(dp[e[i].id],e[st[now]].X*e[i].A+e[st[now]].Y*e[i].B);}solve(mid+1,r);opl=l; opr=mid+1;for(int i=l;i<=r;++i) if(opl>mid) t[i]=e[opr++];else if(opr>r) t[i]=e[opl++];else if(e[opl]<e[opr]) t[i]=e[opl++];else t[i]=e[opr++];for(int i=l;i<=r;++i) e[i]=t[i]; } bool cmp(node p,node q) {return p.K>q.K; }int main() {int n;double m;scanf("%d%lf",&n,&dp[0]);for(int i=1;i<=n;++i) {scanf("%lf%lf%lf",&e[i].A,&e[i].B,&e[i].R);e[i].K=-e[i].A/e[i].B;e[i].id=i;}sort(e+1,e+n+1,cmp);solve(1,n);printf("%.3lf",dp[n]); }?
1492: [NOI2007]貨幣兌換Cash
Time Limit:?5 Sec??Memory Limit:?64 MBSubmit:?5830??Solved:?2342
[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 1001 1 1
1 2 2
2 2 3
Sample Output
225.000HINT
?
?
轉載于:https://www.cnblogs.com/TheRoadToTheGold/p/8453384.html
總結
以上是生活随笔為你收集整理的bzoj千题计划237:bzoj1492: [NOI2007]货币兑换Cash的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 10.Azure应用程序网关(上)
- 下一篇: 线性一致性与全序广播------《Des