BZOJ1492 NOI2007 貨幣兌換

2022-05-16 03:04:13 字數 3298 閱讀 2264

小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天後最多能

夠獲得多少元錢。

輸入第一行兩個正整數n、s,分別表示小y能預知的天數以及初始時擁有的錢數。接下來n行,第k行三個實數ak、b

k、ratek,意義如題目中所述。對於100%的測試資料,滿足:00^9。

【提示】

1.輸入檔案可能很大,請採用快速的讀入方式。

2.必然存在一種最優的買賣方案滿足:

每次買進操作使用完所有的人民幣;

每次賣出操作賣出所有的金券。

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

可以發現每次**和賣出一定是使用全部的資產。

那麼,令$f_i$表示第i天開始時最大可持有多少現金(金券不算),$x_i=\frac$表示這些現金換成金券時有多少$b$券,$y_i=rate_ix_i$表示有多少a券,那麼有

$$f_i = max\left(f_, max\\right)$$

熟悉雙變數線性規劃的可以看出來把$x,y$畫到平面上,用乙個斜率為$-\frac$的直線經過這些點,選出最靠上的,這個點便是最優點。

那麼可以看出,最優解一定在前面所有點的上凸殼上。

但是點的橫縱座標都不是單調的,怎麼辦呢?

1.利用平衡樹維護凸包。

2.cdq分治。

我們發現,只有編號小的會對編號大的產生影響,那麼我們按時間把所有天分成兩半,先遞迴解決前一半,然後將前一半按橫座標排序,求凸包,後一半按$-\frac$排序,更新後一半的答案後再遞迴解決後一半即可。

實現中,按橫座標排序需要歸併排序,按斜率排序只需要開頭排一遍即可。

時間複雜度是常見的$t(n)=2t(\frac n 2) + o(n)$,$t(n) = o(nlogn)$。

附**(其實**裡的注釋是給我自己寫的,畢竟第一次寫cdq):

#include #include #include using std::abs;

const int n = 100050;

const double eps = 1e-6;

struct info

};double f[n];

info p[n], tmp[n];

info cv[n];

inline bool cmp(const info &a, const info &b)

inline double getk(const info &a, const info &b)

inline double calc(const info &a, const info &b)

void solve(int l, int r)

int mid = (l + r) >> 1;

int p1 = l, p2 = mid;

//split [l, r) to [l, mid) and [mid, r)

for (int i = l; i < r; ++i)

for (int i = l; i < r; ++i)

p[i] = tmp[i];

//solve the left half

solve(l, mid);

//get the upper convex hull

int q = 0;

for (int i = l; i < mid; ++i) if (!i || fabs(p[i].x - p[i - 1].x) >= eps)

//update the answers of the right half

for (int i = mid, j = 0; i < r; ++i)

//solve the right half

solve(mid, r);

//sort the p according to x

int i = l, j = mid, e = l;

while (i < mid && j < r)

if (cmp(p[i], p[j]))

tmp[e++] = p[i++];

else

tmp[e++] = p[j++];

while (i < mid)

tmp[e++] = p[i++];

while (j < r)

tmp[e++] = p[j++];

for (int i = l; i < r; ++i)

p[i] = tmp[i];

}int main()

std::sort(p, p + n);

solve(0, n);

//for (int i = 0; i < n; ++i)

// printf("%.9lf\n", f[i]);

printf("%.3lf\n", f[n - 1]);

return 0;

}

BZOJ1492 NOI2007 貨幣兌換

time limit 5 sec memory limit 64 mb submit 4914 solved 2026 submit status discuss 小y最近在一家金券交易所工作。該金券交易所只發行交易兩種金券 a紀念券 以下簡稱a券 和 b紀念券 以下 簡稱b券 每個持有金券的顧客都...

bzoj1492 NOI2007 貨幣兌換Cash

time limit 5 secmemory limit 64 mb submit 3373solved 1424小y最近在一家金券交易所工作。該金券交易所只發行交易兩種金券 a紀念券 以下簡稱a券 和 b紀念券 以下簡稱b券 每個持有金券的顧客都有乙個自己的帳戶。金券的數目可以是乙個實數。每天隨著...

BZOJ 1491 NOI2007 社交網路

顯然這是一道要求多源最短路的題目,資料範圍很小,目測用弗洛伊德演算法。由題意,先求出各個點之間的最短路徑,同時利用乘法原理,計算出由 i 到 j 之間的最短路徑個數。如果又發現了一條最短路,由乘法原理計算增加的路徑個數再加上即可。我寫的 沒有去除自己到自己的路徑,因此需清空,但也可在 floyd 中...