cdq分治

来源:互联网 发布:网络言情小说作家 编辑:程序博客网 时间:2024/04/30 12:26

1.bzoj1492 cash
来源 :hzwer
从 bzoj-1492 学习分治
提示中:必然存在一种最优的方式:每次买入花完所有的金币,且每次卖出物品时,全部卖出
思考:这种原理应该就是贪心:即能够赚钱的话,就赚最多的钱??? 我为什么没看出来 这个东西是 “!显然!”的?

rate[i] = 第i天购买时,将提供给用户的 A:B
A[i],B[i]:第i天 AB的价格
ans: 答案–最多的金币

令 F[i]表示第i天获得的最大的B券数量—-能在最后一天买最多的b 也就是钱最多
通过枚举上一次交易日j
j: F[j] 个B券, 买的时候是A:B=rate[j],意思就是 买入 rate[j]单位A ,同时就会买入1单位B(因为买入,所以会花掉所有的钱[先卖掉券])
既然上一次交易日买入了F[j] 单位B ,则也有rate*F[j]单位A
F[i] ={ max( ans, rate[j]*F[j]*A[i] + F[j]*B[i]) }/ (rate[i]*A[i]+B[i]) : i就表示如果今天全部卖出,再全部买进来,拥有的B券的数量
这个转移无疑是成立的 时间复杂度 O(n^2)

再观察: 如果决策J 优于决策K则:rate[j]*F[j]*A[i] + F[j]*B[i] > rate[k]*F[k]*A[i] + F[k]*B[i]
=( rate[j]*f[j] - rate[k]*f[k] )/(f[j]-f[k]) >-b[i]/a[i] 记为slope(j,k)>-b[i]/a[i]
令G[i] = rate[i]*f[i] ,在二维平面定义点X = (Fi,Gi) 则Slope(j,k) 就是通过Xj 和Xk 的斜率: > - b/a
1. 维护凸包,平衡树 —边界情况多,难写难调

引入分治:
假设运行Slove(l,r) 可以得到F[l]到F[r]的值:
[L,mid]区间的询问是可以由 Slove(l,mid)解决 : 即dp过程
[mid+1,r] 除了[mid+1,r]还有[l,mid]也有影响
过程在代码中详细描写

#include<cstdio>#include<cstring>#include<cstdlib>#include<cmath>#include<iostream>#include<algorithm>using namespace std;/**/#define eps 1e-9#define inf 0x7fffffff#define ll long longusing namespace std;int n,top,stack[100005];double f[100005];struct point{    double x,y,a,b,k,rate;   // 坐标,a,b价格,斜率,rate    int id;              // 时间序列}p[100005],t[100005];double getk(int a,int b)  // 两点之间的斜率{    if(!b)return -1e20;    if(fabs(p[a].x-p[b].x)<eps)return 1e20;    return (p[b].y-p[a].y)/(p[b].x-p[a].x);}inline bool operator<(point a,point b){    return a.k>b.k;  // 斜率从小到大排列}void solve(int l,int r)   // 为了获得[l,r]的F[i]: 这里的f 我们把其定义为某一天最多的金钱。{    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);   // y=f[i]/( a[i]*rate+b[i])        p[l].x=p[l].rate*p[l].y;        // X=rate*y        return;    }//分治到底了显然我们可以直接计算出结果     int l1,l2,mid=(l+r)>>1,j=1;//============================================================================    l1=l;l2=mid+1;    for(int i=l;i<=r;i++)        if(p[i].id<=mid)  // 如果id<mid l1++ 否则 l2++            t[l1++]=p[i];         else                              t[l2++]=p[i];          // 保证 [l,mid] 的id 都是<=mid    for(int i=l;i<=r;i++)   // 相当于调整了一下顺序.  但[l,mid]和[mid+1,r]内部的斜率关系依然适用。        p[i]=t[i];//============================================================================//这一部分是要将一块原顺序分为左右两块     solve(l,mid);//递归左边  得到了这个区间所有的f    top=0;    for(int i=l;i<=mid;i++)    {        while(top>1 && getk(stack[top-1],stack[top])<getk(stack[top-1],i)+eps) //             top--;        stack[++top]=i;     }//左边维护一个凸包        stack[++top]=0;        for(int i=mid+1;i<=r;i++){            while(j<top&&getk(stack[j],stack[j+1])+eps>p[i].k)  //用左边的点作为决策更新右边                j++;                 f[p[i].id]=max(f[p[i].id],p[stack[j]].x*p[i].a+p[stack[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("1.txt","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("%.3lf",f[n]);    return 0;}
原创粉丝点击