1. 程式人生 > >【BZOJ1492】【NOI2007】貨幣兌換(動態規劃,CDQ分治,Splay)

【BZOJ1492】【NOI2007】貨幣兌換(動態規劃,CDQ分治,Splay)

題面

BZOJ
洛谷

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 的變化分別為:

img

假定在第一天時,使用者手中有 100元 人民幣但是沒有任何金券。使用者可以執行以下的操作:

img

注意到,同一天內可以進行多次操作。小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

img

題解

首先考慮一下暴力吧

我們想想,肯定是一天把手上所有錢全部買了之後在後面的某一天把他們全部賣出去。

考慮一個d

p
f[i]表示第i天手上的A券的最大值
當然,記錄手中的最大錢數或者B券的數量都是一樣的
ans記錄任意時刻手中的最多錢數
那麼,
對於一個f[i]而言
ans=max(ans,f[j]i)
f[i]=ans
這樣的dp很顯然是O(n2)60pts

#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;
}

顯然在dp的時候,列舉哪一天是少不了的
現在考慮的是快速計算ans
把關於ans的式子寫一下
ans=max(ans,f[j]A[i]+f[j]/R[j]B[i])
假設j位置的轉移比k位置更優

A[i]f[j]+f[j]R[j]B[i]>A[i]f[k]+f[k]R[k]B[i]
化簡得到
A[i]B[i]>f[j]R[j]f[k]R[k]f[j]f[k]
上面那坨東西好不爽
g[i]=f[i]R[i]
A[i]B[i]>g[j]g[k]f[j]f[k]

左邊這個像個斜率的形式,右邊像直線的兩點式
因此,對於每一天
都可以對應一個點(f[i],g[i])
以及一個斜率A[i]B[i]

維護一個凸殼??
但是f[i]顯然不遞增,g[i]也顯然不遞增
沒法用單調棧來維護。。。很蛋疼啊。。。

要求的東西顯然是所有的直線中,斜率小於目標K
並且連線之後與y軸的截距最大

所以,維護一個上凸殼,在裡面維護斜率,
同時,支援任意插點,動態維護凸殼,二分斜率等操作
看起來是個平衡樹之類的東西。。。

方法一:CDQ分治
我肯定不會閒著蛋疼先去寫平衡樹,
所以我們先來寫一個稍微好寫點的CDQ分治
可以參考CDQ的論文

其實我也是照著網上題解打的

這裡稍微換一下,f[i]表示的是第i天時手上的最大錢數。
其他的轉移大致相同,不再提了。
當然,座標也有一點點小小的變化

考慮如何CDQ分治

很顯然,如果我要求出x位置的值
顯然就要維護出1..x1的值
所以進行CDQ分治
第一點,肯定是按照天數排序分割左右
但是同一側內部不一定按照天數

考慮怎麼計算左右之間的貢獻
相當把左側的凸殼先構造出來,右側按照斜率排序
用一個單調指標掃過去依次更新答案就行了

因為左側要維護凸殼
因此歸併排序的時候按照x座標排序
每次暴力把左側的凸殼構造出來,複雜度O(n)

當只有一個點的時候,這個點的dp值就已經算出來了
同時,