小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 中...