bzoj1942 货币兑换【动态规划+CDQ分治】

来源:互联网 发布:win7开机windows后黑屏 编辑:程序博客网 时间:2024/05/01 00:57

解题思路:

转自 www.cnblogs.com/zig-zag/archive/2013/04/24/3039418.html
稍稍加上个人见解(括号中)。

经典的1D1D动态规划题目,标准做法是平衡树维护凸壳,但实际上还有更简洁的分治法。

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

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代码非常的长,在考场上就非常不容易写出来,于是出现了神一般的cdq分治!

这个神级分治的精髓在于:变在线为离线,化无序为有序。
(cdq分治大体思路即是:先递归处理前半部分询问,接着计算其对后半部分询问的影响,最后递归处理后半部分)

上面我们分析了,因为点和斜率都不是单调的,所以我们只能用一棵平衡树去维护。我们考虑导致无序的原因,是我们按照时间顺序依次回答了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之后把两部分点集归并,这样就保证了每个过程中的点集是有序的。

虽然我叙述的比较烦,但是我们看到这个分治的过程是非常优美的,对于询问我们是先排序(按斜率),后还原;而对于点集我们是不断地归并(按x值),恰好是对称的过程。为什么呢?上面已经说了,因为我们对左右两个半区的需求是不一样的,于是这样就得到了两个不同的有序序列,把无序化为有序。

这样看来,分治算法取代一些复杂的数据结构是一种强有力的趋势。

贴一份cdq分治的代码。

#include<bits/stdc++.h>using namespace std;const int N=100005;const double eps=1e-9;int n,top,stk[N];double f[N];struct point{    double x,y,a,b,k,rate;int id;    inline friend bool operator < (const point &a,const point &b)    {return a.k>b.k;}}p[N],t[N];double slope(int a,int b){    if(!b)return -1e20;    if(abs(p[a].x-p[b].x)<eps)return 1e20;    return (p[b].y-p[a].y)/(p[b].x-p[a].x);}void solve(int l,int r){    if(l==r)    {        f[l]=max(f[l],f[l-1]);        p[l].y=f[l]/(p[l].a*p[l].rate+p[l].b);        p[l].x=p[l].rate*p[l].y;        return;    }    int mid=l+r>>1,l1=l,l2=mid+1,j=1;    for(int i=l;i<=r;i++)        if(p[i].id<=mid)t[l1++]=p[i];        else t[l2++]=p[i];    for(int i=l;i<=r;i++)p[i]=t[i];    solve(l,mid);    top=0;    for(int i=l;i<=mid;i++)    {        while(top>=2&&slope(stk[top-1],stk[top])<slope(stk[top-1],i)+eps)top--;        stk[++top]=i;    }    stk[++top]=0;    for(int i=mid+1;i<=r;i++)    {        while(j<top&&slope(stk[j],stk[j+1])+eps>p[i].k)j++;        f[p[i].id]=max(f[p[i].id],p[stk[j]].x*p[i].a+p[stk[j]].y*p[i].b);    }    solve(mid+1,r);    l1=l,l2=mid+1;    for(int i=l;i<=r;i++)        if(((p[l1].x<p[l2].x||(fabs(p[l1].x-p[l2].x)<eps&&p[l1].y<p[l2].y))||l2>r)&&l1<=mid)t[i]=p[l1++];       else t[i]=p[l2++];    for(int i=l;i<=r;i++)p[i]=t[i];}int main(){    //freopen("lx.in","r",stdin);    scanf("%d%lf",&n,&f[0]);    for(int i=1;i<=n;i++)    {        scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].rate);        p[i].k=-p[i].a/p[i].b;p[i].id=i;    }    sort(p+1,p+n+1);    solve(1,n);    printf("%.3f",f[n]);    return 0;}
阅读全文
0 0