1. 程式人生 > >BZOJ1492: [NOI2007]貨幣兌換Cash

BZOJ1492: [NOI2007]貨幣兌換Cash

小數 arr ati 隨著 輸入 分治算法 || 圖片 strong

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、BK、RateK,意義如題目中所述。對於100%的測試數據,滿足:\(0<AK≤10,0<BK≤10,0<RateK≤100,MaxProfit≤10^9\)
\(n\leq 10^5\)

【提示】
1.輸入文件可能很大,請采用快速的讀入方式。
2.必然存在一種最優的買賣方案滿足:
每次買進操作使用完所有的人民幣;每次賣出操作賣出所有的金券。

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

Sample Input

3 100
1 1 1
1 2 2
2 2 3

Sample Output

225.000

HINT
技術分享圖片


Solution

首先題設比較復雜,不仔細讀題,後面的方程肯定寫不出來。

考慮到,如果有一天賣出會讓你盈利,肯定是全部賣出時盈利更多。
同理,如果有一天買入會使得之後某一天賣出時賺得更大利潤,那麽這一天一定會全部買入。
沒有上面兩點這題根本不可做。

對於動歸的狀態,記作 \(dp[i].ma\)\(dp[i].mb\) 分別表示第 \(i\) 天如果全部買入,可以最多獲得 \(dp[i].ma\)\(A\) 券,與此同時可以買到 \(dp[i].mb\)\(B\) 券。\(dp[i].ma\) 是最主要的狀態。姑且把m看作質量吧,不知道該咋起名字

這樣就有了 DP 方程(註意 \(i,j\) 別亂了):

\[ \begin{equation*} \begin{aligned} dp[i].mb&=\frac{val}{A[i]*rate[i]+B[i]}\dp[i].ma&=dp[i].mb\times rate[i]\val&=max\{dp[j].ma*A[i]+dp[j].mb*B[i]\} \end{aligned} \end{equation*} \]

這樣得到的是一個 \(O(n^2)\) 的算法,val的最大值為最終答案

考慮對於一個決策點 \(t\),如果有兩個決策方案 \(x,y\) ,那麽 \(x\)\(y\) 優當且僅當:
\[ \begin{equation*} \begin{aligned} val_x&>val_y\dp[x].ma\times A[t]+dp[x].mb\times B[t]&>dp[y].ma\times A[t]+dp[y].mb\times B[t]\A[t]\times(dp[x].ma-dp[y].ma)&>B[t]\times(dp[y].mb-dp[x].mb) \end{aligned} \end{equation*} \]

這時,這已經很像斜率優化了,但是因為沒有各種單調性,所以沒法搞事。
我們直接令 \(dp[x].ma<dp[y].ma\) 使得其有一個單調性。
\[ \begin{equation*} \begin{aligned} \frac{A[t]}{B[t]}&<\frac{dp[y].mb-dp[x].mb}{dp[x].ma-dp[y].ma}\-\frac{A[t]}{B[t]}&>\frac{dp[x].mb-dp[y].mb}{dp[x].ma-dp[y].ma} \end{aligned} \end{equation*} \]
這就可以斜率優化了這是一個上凸殼

接下來,由於 \(dp[x].ma\) 實際上並不單調,所以需要用平衡樹維護上凸包我沒寫這個
或者使用 CDQ分治

首先對於區間 \([L,R]\),可以通過 \([L,mid]\) 的信息做出來一個上凸包,用於更新 \([mid+1,R]\) 的決策,不一定是最終的最優,但要保證是 \([L,mid]\) 轉移過來的最優情況。
考慮把 \(-\frac{A[t]}{B[t]}\) 看做第一維,\(dp[x].ma\) 看做第二維。
將第一維排序(升序降序都可以寫出來),對第二維進行類似歸並的過程。
具體過程是\(solve(l,r)\)
\(\rightarrow\)按下標進行對每一天信息的劃分(小於 \(mid\) 劃到 \([L,mid]\) 否則 \([mid+1,R]\)。由於之前排過序,劃分後\(-\frac{A[t]}{B[t]}\)仍然有序)
\(\rightarrow solve(l,mid)\)
\(\rightarrow\)構建\([L,mid]\)凸包。進行決策,由於\(-\frac{A[t]}{B[t]}\)和凸包都是單調的,可以\(O(n)\)算一下\([mid+1,R]\)所有的最優解。
\(\rightarrow solve(mid+1,r)\)
\(\rightarrow\)按照 \(dp[x].ma\) 升序排序,這是可以進行斜率優化的基礎。

這樣保證了斜率優化的可行性(\(dp[x].ma\)單調遞增),同時又可以不讓決策的先後錯亂(比如先在第三天操作再去第二天操作),是一個優秀的分治算法。時間復雜度 \(O(nlogn)\)

/**************************************************************
    Problem: 1492
    User: zzzc18
    Language: C++
    Result: Accepted
    Time:1512 ms
    Memory:10592 kb
****************************************************************/

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXN = 100000+9;
const double EPS = 1e-9;
const double INF = 1e20;
int n;
double ans[MAXN];

struct DATA{
    double A,B,rate;
    int id;
}num[MAXN];

bool DATAcmp(const DATA &X,const DATA &Y){
    return (-X.A/X.B)<(-Y.A/Y.B);
}

struct DP{
    double ma,mb;//表示A的數量以及B的數量
}dp[MAXN];

double K(int x,int y){
    if(dp[x].ma==dp[y].ma)return -INF;
    return (dp[x].mb-dp[y].mb)/(dp[x].ma-dp[y].ma);
}

bool jud(int x,int y){
    if(fabs(dp[x].ma-dp[y].ma)<EPS)
        return dp[x].ma<dp[y].ma; 
    else
        return dp[x].ma<dp[y].ma;
}

void CDQ(int l,int r){
    if(l==r){
        ans[l]=max(ans[l],ans[l-1]);
        dp[l].mb=ans[l]/(num[l].A*num[l].rate+num[l].B);
        dp[l].ma=dp[l].mb*num[l].rate;
        return;
    }
    static DATA tmp1[MAXN];
    static DP tmp2[MAXN];
    int mid=l+r>>1;
    int p1=l,p2=mid+1;
    for(int i=l;i<=r;i++){
        if(num[i].id<=mid)
            tmp1[p1++]=num[i];
        else
            tmp1[p2++]=num[i];
    }
    for(int i=l;i<=r;i++)
        num[i]=tmp1[i];
    CDQ(l,mid);
    static int top;
    static int sta[MAXN];
    top=1;
    for(int i=l;i<=mid;i++){
        while(top>2 && K(sta[top-1],sta[top-2])<K(sta[top-1],i))
            top--;
        sta[top++]=i;
    }
    int now=1;
    for(int i=r;i>mid;i--){
        while(now<top-1 && K(sta[now],sta[now+1])>=-num[i].A/num[i].B)
            now++;
        int t=num[i].id;
        ans[t]=max(ans[t],dp[sta[now]].ma*num[i].A+dp[sta[now]].mb*num[i].B);
    }
    CDQ(mid+1,r);
    p1=l,p2=mid+1;
    for(int i=l;i<=r;i++){
        if(p2>r || (p1<=mid && jud(p1,p2)))
            tmp2[i]=dp[p1++];
        else
            tmp2[i]=dp[p2++];
    }
    for(int i=l;i<=r;i++)
        dp[i]=tmp2[i];
}

void solve(){
    sort(num+1,num+n+1,DATAcmp);
    CDQ(1,n);
    printf("%.3lf\n",ans[n]);
}

int main(){
    scanf("%d",&n);
    scanf("%lf",&ans[0]);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf%lf",&num[i].A,&num[i].B,&num[i].rate);
        num[i].id=i;
    }
    solve();
    return 0;
}

BZOJ1492: [NOI2007]貨幣兌換Cash