题意:

小 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 的变化分别为:

时间                  Ak                  Bk                Ratek
第一天                 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天后最多能够获得多少元钱。

思路:转载自http://www.cnblogs.com/zig-zag/archive/2013/04/24/3039418.html

首先分析一下题目,对于任意一天,一定是贪心地买入所有货币或者卖出所有货币是最优的,因为有便宜我们就要尽量去占,有亏损就一点也不去碰。于是我们得到方程:

f[i]=max{f[j]/(a[j]*rate[j]+b[j])*rate[j]*a[i]+f[j]/(a[j]*rate[j]+b[j])*b[i]}

其中,x[j]=f[j]/(a[j]*rate[j]+b[j])*rate[j]表示第j天最多可以拥有的A货币的数量

   y[j]=f[j]/(a[j]*rate[j]+b[j])表示第j天最多可以拥有的B货币的数量

那么方程可化简为f[i]=max{x[j]*a[i]+y[j]*b[i]},那么我们就是要选择一个最优的决策点(x[j],y[j])来更新f[i]得到最优解。

变形:y[j]=f[i]/b[i]-x[j]*a[i]/b[i],这是一个直线的斜截式方程,由于我们是用j去更新i,那么就相当于每次用一条斜率为-a[i]/b[i]的直线去切由若干(x[j],y[j])点组成的集合,能得到的最大截距的点,就是最优决策点,进一步,就是要维护一个由若干(x[j],y[j])点组成凸壳,因为最优决策点一定在凸壳上。

但是对于斜率-a[i]/b[i]和点(x[j],y[j])都是无序的,于是我们只能用一棵平衡树来维护凸壳,每次找到斜率能卡到的点(此点左侧的斜率和右侧的斜率恰好夹住-a[i]/b[i]斜率)。

具体splay实现:我们维护x坐标递增的点集,每次把新点插入到相应位置,更新凸壳的时候,分别找到新点左右能与它组成新的凸壳的点,把中间的点删掉;如果这个点完全在旧的凸壳内,那么把这个点删掉。每次找最优决策点的时候就拿-a[i]/b[i]去切凸壳就行了。

但这样搞实在是麻烦了许多,而且许多人得splay代码非常的长,在考场上就非常不容易写出来,于是出现了神一般的陈丹琪分治!

这个神级分治的精髓在于:变在线为离线,化无序为有序。

上面我们分析了,因为点和斜率都不是单调的,所以我们只能用一棵平衡树去维护。我们考虑导致无序的原因,是我们按照顺序依次回答了1..n的关于f值的询问。但是事实上我们并没有必要这么做,因为每个1..n的f[i]值,可能成为最优决策点一定在1..i范围内,而对于每个在1..i范围内的决策点,一定都有机会成为i+1..n的f值得最优决策点。这样1..i的f值一定不会受1..i的决策点的影响,i+1..n的点一定不会i+1..n的f值。于是可以分治!

对于一个分治过程solve(l,r),我们用l..mid的决策点去更新mid+1..r这部分的f值,这样递归地更新的话,我们一定可以保证在递归到i点的时候,1..i-1的点都已经更新过i点的f值了。我们看到,分治的过程中,左半区(l..mid)和右半区(mid+1..r)这两个区间的作用是不同的,我们要用左半区已经更新好的f值去求出点(x,y),然后用右半区的斜率去切左半区的点集更新f值。对于左半边我们需要的只是点(x,y),右半区我们需要的只是斜率-a[i]/b[i],两部分的顺序互不影响。于是,我们在处理好左半边的东西的时候保证点集按坐标排好序,在处理右半区之前保证询问按照斜率排好序,这样相当于用一系列连续变化的直线去切一些连续点组成的凸壳,那么我们就可以简单地用一个栈来维护连续点组成的凸壳,用扫描的方法更新f值。我们一开始就排好询问的顺序,然后保证在solve之前还原左半区询问集合的顺序,这样就保证了按照原顺序得到f值;在solve之后把两部分点集归并,这样就保证了每个过程中的点集是有序的。

 const eps=1e-9;
type arr=record
x,y,rate,a,b,k:double;
id:longint;
end; var p,q:array[..]of arr;
stack:array[..]of longint;
dp:array[..]of double;
n,m,i,top:longint; function fabs(x:double):double;
begin
if x< then exit(-x);
exit(x);
end; function slope(x,y:longint):double;
begin
if fabs(y)<eps then exit(-1e20);
if fabs(q[x].x-q[y].x)<eps then exit(1e20);
exit((q[x].y-q[y].y)/(q[x].x-q[y].x));
end; function max(x,y:double):double;
begin
if x>y then exit(x);
exit(y);
end; procedure swap(var x,y:arr);
var t:arr;
begin
t:=x; x:=y; y:=t;
end; procedure cdq(l,r:longint);
var mid,l1,r1,i,j:longint;
begin
if l=r then
begin
dp[l]:=max(dp[l],dp[l-]);
q[l].y:=dp[l]/(q[l].rate*q[l].a+q[l].b);
q[l].x:=q[l].y*q[l].rate;
exit;
end;
mid:=(l+r)>>;
l1:=l; r1:=mid+;
for i:=l to r do
if q[i].id<=mid then
begin
p[l1]:=q[i]; inc(l1);
end
else
begin
p[r1]:=q[i]; inc(r1);
end;
for i:=l to r do q[i]:=p[i];
cdq(l,mid);
top:=;
for i:=l to mid do
begin
while (top>)and
(slope(stack[top-],stack[top])<slope(stack[top-],i)+eps) do dec(top);
inc(top);
stack[top]:=i;
end;
inc(top); stack[top]:=; j:=;
for i:=mid+ to r do
begin
while (j<top)and(slope(stack[j],stack[j+])+eps>q[i].k) do inc(j);
dp[q[i].id]:=max(dp[q[i].id],q[stack[j]].x*q[i].a+q[stack[j]].y*q[i].b);
end;
cdq(mid+,r);
l1:=l; r1:=mid+;
for i:=l to r do
if (((q[l1].x<q[r1].x)or((fabs(q[l1].x-q[r1].x)<eps)and
(q[l1].y<q[r1].y)))or(r1>r))and(l1<=mid) then
begin
p[i]:=q[l1]; inc(l1);
end
else
begin
p[i]:=q[r1]; inc(r1);
end;
for i:=l to r do q[i]:=p[i]; end; procedure qsort(l,r:longint);
var i,j:longint;
mid:double; begin
i:=l; j:=r; mid:=q[(l+r)>>].k;
repeat
while mid<q[i].k do inc(i);
while mid>q[j].k do dec(j);
if i<=j then
begin
swap(q[i],q[j]);
inc(i); dec(j);
end;
until i>j;
if l<j then qsort(l,j);
if i<r then qsort(i,r);
end; begin
assign(input,'bzoj1492.in'); reset(input);
assign(output,'bzoj1492.out'); rewrite(output);
readln(n,dp[]);
for i:= to n do
begin
read(q[i].a,q[i].b,q[i].rate);
q[i].k:=-q[i].a/q[i].b;
q[i].id:=i;
end;
qsort(,n);
cdq(,n);
writeln(dp[n]::);
close(input);
close(output);
end.

【BZOJ1492】货币兑换Cash(CDQ分治)的更多相关文章

  1. [BZOJ1492] [NOI2007] 货币兑换Cash(cdq分治+斜率优化)

    [BZOJ1492] [NOI2007] 货币兑换Cash(cdq分治+斜率优化) 题面 分析 dp方程推导 显然,必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币:每次卖出操作卖出所有 ...

  2. bzoj1492[NOI2007]货币兑换Cash cdq分治+斜率优化dp

    1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 5541  Solved: 2228[Submit][Sta ...

  3. BZOJ 1492: [NOI2007]货币兑换Cash [CDQ分治 斜率优化DP]

    传送门 题意:不想写... 扔链接就跑 好吧我回来了 首先发现每次兑换一定是全部兑换,因为你兑换说明有利可图,是为了后面的某一天两种卷的汇率差别明显而兑换 那么一定拿全利啊,一定比多天的组合好 $f[ ...

  4. BZOJ 1492 货币兑换 Cash CDQ分治

    这题n2算法就是一个维护上凸包的过程. 也可以用CDQ分治做. 我的CDQ分治做法和网上的不太一样,用左边的点建立一个凸包,右边的点在上面二分. 好处是思路清晰,避免了凸包的插入删除,坏处是多了一个l ...

  5. BZOJ 1492 [NOI2007]货币兑换Cash (CDQ分治/splay 维护凸包)

    题目大意:太长了略 splay调了两天一直WA弃疗了 首先,我们可以猜一个贪心,如果买/卖,就一定都买/卖掉,否则不买/卖 反正货币的行情都是已知的,没有任何风险,所以肯定要选择最最最优的方案了 容易 ...

  6. BZOJ1492: [NOI2007]货币兑换Cash(CDQ分治,斜率优化动态规划)

    Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个 ...

  7. [bzoj1492][NOI2007]Cash[CDQ分治;dp;斜率优化]

    首先,设f[x]表示x天能获得的A券最大值,有动规方程: $f[i]=max\{f[j]*A[i]+f[j]*B[i]/R[j]\}*R[i]/(R[i]*A[i]+B[i])$, 设 $j<k ...

  8. bzoj-1492 货币兑换Cash (2)——CDQ分治

    题意: 略 见上一篇 题解: 方程还是那个方程f[i]=A[i] * X[j] + B[i] * Y[j]. 化简为Y[i]=(-A[i]/B[i]) * X[i] + f[i]/B[i]这一坨: 既 ...

  9. BZOJ1492:[NOI2007]货币兑换 (CDQ分治+斜率优化DP | splay动态维护凸包)

    BZOJ1492:[NOI2007]货币兑换 题目传送门 [问题描述] 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和B纪念券(以下简称B券).每个持有金券的 ...

  10. [NOI2007]货币兑换 「CDQ分治实现斜率优化」

    首先每次买卖一定是在某天 $k$ 以当时的最大收入买入,再到第 $i$ 天卖出,那么易得方程: $$f_i = \max \{\frac{A_iRate_kf_k}{A_kRate_k + B_k} ...

随机推荐

  1. iOS面试题之内存管理

    本文围绕内存管理的几种方法展开叙述. 1.内存管理是什么? 内存管理,就是对内存资源进行优化. 2.内存管理的三种方法? Objective-C的内存管理主要有三种方式ARC(自动内存计数).MRC( ...

  2. MyBatis -- 必知必会

    MyBatis的前身是Apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis.201 ...

  3. 【经验总结】VS2010下建立MFC程序

    孙鑫的MFC教学视频非常不错,但是由于视频中孙鑫老师采用VC6.0版本,而现在 许多人都转向了使用VS,VS为我们生成了许多不需要的代码,这也导致在这节课的学习编程中总是遇到一些困难.那么,如何去掉这 ...

  4. Scala基础篇-05求值策略

    Scala的求值策略有2种: call by value call by name 如何区分? 例子: def bar(x:Int,y: => Int) = def loop(): Int=lo ...

  5. pspad的一个怪现象:在一些空行的位置出现个别不该出现的字符

    在用pspad编辑一个外来文件时,发现有许多空行的结尾会出现一些单个字符,字符内容与翻页前那一页相应位置的字符相同. 好奇怪.上网找不到原因.pspad太好用了,不想因此放弃. 仔细观察,这些空行往往 ...

  6. whatis命令

    whatis——于查询一个命令执行什么功能 示例1: # whatis ls 显示ls命令的功能,和执行man命令时NAME信息差不多

  7. CATransaction 知识

    CATransaction 事务类,可以对多个layer的属性同时进行修改.它分隐式事务,和显式事务. 区分隐式动画和隐式事务:隐式动画通过隐式事务实现动画 . 区分显式动画和显式事务:显式动画有多种 ...

  8. macos openssl 生成rsa证书 -mark

    创建私钥 openssl genrsa -out rsa_private_key.pem 1024 创建无密码私钥 openssl pkcs8 -topk8 -inform PEM –nocrypt ...

  9. CocosCreator工程内的命名

    命名结构总体的命名结构遵循以下格式 前缀-内容-尾缀 - 前缀:用来定义node的属性- 内容:node的名字- 尾缀:序列或状态1231. 前缀说明:在开始的时候定义/声明这个节点的属性前缀可以是一 ...

  10. ElasticSearch可视化工具 Kibana

    Kibana要和ElasticSearch 版本一致,默认的端口号是:5601