BZOJ 1492 货币兑换Cash(CDQ分治+斜率优化dp)
2017-12-01 21:40
302 查看
1492: [NOI2007]货币兑换Cash
Time Limit: 5 Sec Memory Limit:64 MB
Submit: 5418 Solved: 2189
[Submit][Status][Discuss]
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≤1
0^9。
【提示】
1.输入文件可能很大,请采用快速的读入方式。
2.必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。Sample Input
3 1001 1 1
1 2 2
2 2 3
Sample Output
225.000HINT
Source
之前已经用斜率优化dp+平衡树维护凸点把这题给解决了,但是呢,这题的故事并没有结束。首先膜拜CDQ陈丹琦大神Orz……昨天突然得知,其父亲是NUDT数学系的教授……与大神之间的距离居然如此之近。还是上论文:从《Cash》谈一类分治算法的应用
显然,用平衡树维护凸点的做法是可以的,但是在比赛中splay却是非常的不好写,编程复杂度极其的高。所以在这种情况下,CDQ就想出了这样一种分治法。首先,我们先回顾一下这道题目。根据分析可以有状态转移方程:dp[i]=max(dp[i-1],x[j]*A[i]+y[j]*B[i]),其中dp表示到第i天为止的最大利润,x、y为对应天的在最优情况下取A券的数量和B券的数量。然后可以用斜率优化,写出如下斜率表达式:(y[j]-y[k])/(x[j]-x[k])<-A[i]/B[i]。矛盾点在于,x[]和-A[i]/B[i]没有任何单调性,无法用单调队列进行斜率优化。
我们再解释一下这个斜率表达式,对于一个状态i,我们相当于找一个满足斜率不等式的最大的一个决策j,而这个决策显然满足0<j<i。有了这个性质,我们就可以考虑进行分治。因为对于每一个状态i,她都只依赖于1~i-1的决策,所以对于一个需要解决的区间[l,r],我们把它分为[l,mid]和[mid+1,r]两个部分。首先解决前面的部分,然后根据前一半的结果去更新后一半的答案,这就是CDQ分治的精髓。具体到本题,我们首先要对所有状态按照-A[i]/B[i]排序,然后再进行分治,这个目的在于使得最后选取最优决策的时候可以利用这个单调性。对于前半部分,我们先分治它,然后对于后半部分就以前半部分的结果为决策,按照普通斜率优化那样用单调队列优化更新结果。可能你会说,我们还没有解决x[]没有单调性的问题,如果这个不解决的话点加入的顺序就会混乱导致出错。但是不要着急,我们再更新完后半部分的结果之后,再分治后半部分。最后,最关键的一步,我们已经处理完[l,r]区间之后,我们在对每一个已经解决的决策以x为关键字进行归并排序。如此一来,我们每次分治处理完左半区间后,左半区间就已经是按照x的顺序重新排序了的,这样就可以安全的使用单调队列进行优化。这一步不可谓不巧妙,先分后治,分到单位决策直接解决,然后前面更新后面,处理完全部后在决策重排,供后面的状态决策。
对于本题,更形象的说明这个过程。对于一个状态i,我们要找一个满足条件的最大的决策j,而这个决策在1~i-1之间。而CDQ分治的过程,相当于把1~i-1这个区间分成了几个小区间,然后用每个小区间的最有决策来更新最后结果,如此一来可以保证答案的正确性。至于复杂度的话,可以证明是O(NlogN)。具体来说,这个分治法充分利用了这种先分后治的思想,在很多地方都可以应用,还需要我不断探索。具体见代码:
#include<bits/stdc++.h> #define INF 1e18 #define N 100010 #define eps 1e-10 using namespace std; struct node { double k,rate,x,y,a,b; int id; } p ,tmp ; bool cmp1(node a,node b) { return a.k>b.k; } bool cmp2(node a,node b) { return a.x<b.x; } double dp ; int n,m,q ; double slope(int a,int b) { if (!b) return -INF; if (fabs(p[a].x-p[b].x)<eps) return INF; return (p[a].y-p[b].y)/(p[a].x-p[b].x); } void cdq(int l,int r) { if (l==r) //单位区间,直接计算结果,并记录(x,y)坐标 { dp[l]=max(dp[l],dp[l-1]); p[l].y=dp[l]/(p[l].a*p[l].rate+p[l].b); p[l].x=p[l].y*p[l].rate; return; } int mid=(l+r)>>1,t1=l-1,t2=mid; for(int i=l;i<=r;i++) //按照id分为左右两个部分,而且各个部分内按照-A[i]/B[i]的顺序排好了序 if (p[i].id>mid) tmp[++t2]=p[i]; else tmp[++t1]=p[i]; for(int i=l;i<=r;i++) p[i]=tmp[i]; cdq(l,mid); //分治左半区间 int h=1,t=0; //单调队列指针 for(int i=l;i<=mid;i++) //利用左半区间的结果作为决策,维护斜率单调下降区间 { while(t>1&&slope(q[t-1],q[t])<slope(q[t],i)+eps) t--; q[++t]=i; } q[++t]=0; for(int i=mid+1;i<=r;i++) //由于已经按照降序排好序,所以直接单调下去更新每一个状态的结果 { while(h<t&&slope(q[h],q[h+1])+eps>p[i].k) h++; dp[p[i].id]=max(dp[p[i].id],p[q[h]].x*p[i].a+p[q[h]].y*p[i].b); } cdq(mid+1,r); //分治右边 merge(p+l,p+mid+1,p+mid+1,p+r+1,tmp+l,cmp2); //关键一步,整个区间计算完毕后要按照x来升序排序 for(int i=l;i<=r;i++) p[i]=tmp[i]; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { double a,b,rate; scanf("%lf%lf%lf",&a,&b,&rate); p[i]=node{-a/b,rate,0,0,a,b,i}; } sort(p+1,p+1+n,cmp1); dp[0]=m; cdq(1,n); printf("%.3f",dp ); }
这个方法真的是太厉害了,对于CDQ本人不得不服○| ̄|_ ○| ̄|_ ○| ̄|_
相关文章推荐
- [DP 斜率优化 CDQ分治||动态维护凸包] BZOJ 1492 [NOI2007]货币兑换Cash
- [BZOJ1492][NOI2007]货币兑换Cash(斜率优化dp+splay|cdq分治维护凸包)
- [BZOJ1492][NOI2007][CDQ分治][斜率优化][DP]货币兑换Cash
- [BZOJ 1492][NOI2007]货币兑换Cash:CDQ分治|DP斜率优化
- BZOJ 1492: [NOI2007]货币兑换Cash [CDQ分治 斜率优化DP]
- [BZOJ1492][NOI2007][斜率优化][动态凸包][DP][分治]货币兑换cash
- BZOJ_1492_[NOI2007]货币兑换Cash_CDQ分治+斜率优化
- [bzoj1492][cdq分治][斜率优化][NOI2007]货币兑换Cash
- BZOJ1492:[NOI2007]货币兑换 (CDQ分治+斜率优化DP | splay动态维护凸包)
- BZOJ1492:[NOI2007]货币兑换Cash (CDQ分治+斜率优化DP/平衡树维护凸壳)
- bzoj 1492: [NOI2007]货币兑换Cash【贪心+斜率优化dp+cdq】
- [BZOJ1492][NOI2007]货币兑换Cash && CDQ分治+斜率优化
- 【BZOJ1492】[NOI2007]货币兑换Cash 斜率优化+cdq分治
- 斜率优化(CDQ分治,Splay平衡树):BZOJ 1492: [NOI2007]货币兑换Cash
- NOI 2007 货币兑换Cash (bzoj 1492) - 斜率优化 - 动态规划 - CDQ分治
- BZOJ 1492 [NOI2007]货币兑换Cash(斜率优化dp+splay维护凸壳)
- BZOJ 1492 货币兑换 cash 斜率优化DP
- 【bzoj1492】【NOI2007】【货币兑换】【斜率优化+cdq分治】
- 【BZOJ 1492】 [NOI2007]货币兑换Cash 斜率优化DP
- [BZOJ1492]-[NOI2007]货币兑换Cash-斜率优化+CDQ