P4027 [NOI2007]货币兑换
题目描述
小 (Y) 最近在一家金券交易所工作。该金券交易所只发行交易两种金券:(A) 纪念券(以下简称 (A) 券)和 (B) 纪念券(以下简称 (B) 券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。
每天随着市场的起伏波动,两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 (K) 天中 (A) 券和 (B) 券的价值分别为 (A_K) 和 (B_K) (元/单位金券)。
为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法。
比例交易法分为两个方面:
a) 卖出金券:顾客提供一个([0,100])内的实数 (OP) 作为卖出比例,其意义为:将 (OP\%) 的 (A) 券和 (OP\%) 的 (B) 券以当时的价值兑换为人民币;
b) 买入金券:顾客支付 (IP) 元人民币,交易所将会兑换给用户总价值为 (IP) 的金券,并且,满足提供给顾客的 (A) 券和 (B) 券的比例在第 (K) 天恰好为 (Rate_K);
例如,假定接下来 (3) 天内的 (A_k) 、(B_k) 、(Rate_K) 的变化分别为:
时间 | (A_k) | (B_k) | (Rate_k) |
---|---|---|---|
第一天 | 1 | 1 | 1 |
第二天 | 1 | 2 | 2 |
第三天 | 2 | 2 | 3 |
假定在第一天时,用户手中有 (100) 元人民币但是没有任何金券。
用户可以执行以下的操作:
时间 | 用户操作 | 人民币(元) | A券的数量 | B券的数量 |
---|---|---|---|---|
开户 | 无 | (100) | (0) | (0) |
第一天 | 买入 (100)元 | (0) | (50) | (50) |
第二天 | 卖出 (50\%) | (75) | (25) | (25) |
第二天 | 买入(60)元 | (15) | (55) | (40) |
第三天 | 卖出 (100\%) | (205) | (0) | $0 |
注意到,同一天内可以进行多次操作。
小 (Y) 是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经知道了未来 (N) 天内的 (A) 券和 (B) 券的价值以及 (Rate)。他还希望能够计算出来,如果开始时拥有 (S) 元钱,那么 (N) 天后最多能够获得多少元钱。
输入输出格式
输入格式:
第一行两个正整数 (N)、(S),分别表示小 (Y) 能预知的天数以及初始时拥有的钱数。
接下来 (N) 行,第 (K) 行三个实数 (A_K) 、(B_K) 、(Rate_K) ,意义如题目中所述。
输出格式:
只有一个实数 (MaxProfit),表示第 (N) 天的操作结束时能够获得的最大的金钱数目。答案保留 (3) 位小数。
说明
本题没有部分分,你的程序的输出只有和标准答案相差不超过(0.001)时,才能获得该测试点的满分,否则不得分。
测试数据设计使得精度误差不会超过 (10^{-7}) 。
对于(40\%)的测试数据,满足 (N le 10);
对于(60\%)的测试数据,满足 (N le 1 000);
对于(100\%)的测试数据,满足 (N le 100 000);
对于(100\%)的测试数据,满足:
(0 < A_K le 10);
(0 < B_K le 10);
(0 < Rate_Kle 100);
(MaxProfit le 10^9) ;
提示:
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
居然有提示,虽然还是比较显而易见的..
太久没写斜率优化式子都没转过去..
令(dp_i)代表第(i)天(还未决定买不买)的最大拥有金钱数量
然后把和(i)与和(j)有关的项分开表示
转换一下
这就很标准的斜率优化了叭,但发现这些东西没啥单调性,于是不能简单的单调队列了。
可以拿平衡树动态维护,不过CDQ的做法会更好写常数也更小。
说一下CDQ大概的实现
左边的按(x)坐标排序以后(O(n))弄出个斜率递减的凸包,右边直接按斜率从大到小排序,然后像归并那样边合并边做就好了。这样应该写起来是最简单的,尝试写二分但发现有点麻烦。
注意要还原右边。
Code:
#include <cstdio>
#include <algorithm>
using std::max;
const int N=1e5+10;
const double eps=1e-7;
const double inf=1e10;
struct node
{
double a,b,c,k,ans;int id;
}q[N];
int s[N],tot,n;
double ans;
std::pair <double,double > poi[N];
bool cmp1(node n1,node n2){return n1.k>n2.k;}
bool cmp2(node n1,node n2){return n1.id<n2.id;}
double slope(int i,int j)
{
double x=poi[i].first,y=poi[i].second,xx=poi[j].first,yy=poi[j].second;
if(xx-x<eps) return inf;
return (yy-y)/(xx-x);
}
void CDQ(int l,int r)
{
if(l==r){q[l].ans=max(ans,q[l].ans),ans=max(ans,q[l].ans);return;}
int mid=l+r>>1;
CDQ(l,mid);
for(int i=l;i<=mid;i++)
poi[i]=std::make_pair(-q[i].ans/(q[i].a*q[i].c+q[i].b),q[i].ans/(q[i].a*q[i].c+q[i].b)*q[i].c);
std::sort(poi+l,poi+mid+1);
tot=0;
for(int i=l;i<=mid;i++)
{
while(tot>1&&slope(s[tot-1],s[tot])+eps<slope((s[tot]),i)) --tot;
s[++tot]=i;
}
std::sort(q+mid+1,q+r+1,cmp1);
int lp=1;
for(int i=mid+1;i<=r;i++)
{
while(lp<tot&&q[i].k+eps<slope(s[lp],s[lp+1])) ++lp;
q[i].ans=max(q[i].ans,-poi[s[lp]].first*q[i].b+poi[s[lp]].second*q[i].a);
}
std::sort(q+mid+1,q+r+1,cmp2);
CDQ(mid+1,r);
}
int main()
{
scanf("%d%lf",&n,&ans);
for(int i=1;i<=n;i++)
{
scanf("%lf%lf%lf",&q[i].a,&q[i].b,&q[i].c);
q[i].id=i,q[i].k=q[i].b/q[i].a;
}
CDQ(1,n);
printf("%.3lf
",ans);
return 0;
}
2018.11.28