bzoj1492(cdq分治 && 平衡树维护dp)

经典的1D1D动态规划题目(这里就是1D1D动态规划经典的第二种, 平衡树去维护 f(n) = min{a[n]*x(i) + b[n]*y(i)})

http://wenku.baidu.com/link?url=oWsqGQ7qjdM-ASnQBP6KmZRc8yYXyxbM3Czq_rhWvXn7IywrI8oqn3EuUs7AtecS04vznCfuHe3l-9DvBjZAwiX4ZApFpQHH1h_vBZ-7DSa

标准做法是平衡树维护凸壳,但实际上还有更简洁的分治法。

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

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货币的数量

 

说到cdq分治和整体二分的区别是, cdq分治是对操作进行离线二分, 而整体二分是对答案进行整体的二分

这下终于茅塞顿开了

 

http://www.cnblogs.com/zig-zag/archive/2013/04/24/3039418.html 这篇代码写的很赞 真的是如励志铭一样:“AC without art, no better than WA !”

http://blog.csdn.net/thy_asdf/article/details/46686351 这篇是功能强大的图解 ~\(≧▽≦)/~

splay代码(盗用上面链接的做模板):

cash1
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define maxn 120000
#define eps 1e-9
#define inf 1e9
using namespace std;
int fa[maxn],c[maxn][2];
double f[maxn],x[maxn],y[maxn],lk[maxn],rk[maxn],a[maxn],b[maxn],rate[maxn];
int n,m,rot,num;

inline double fabs(double x)
{
    return (x>0)?x:-x;
}

inline void zigzag(int x,int &rot)
{
    int y=fa[x],z=fa[y];
    int p=(c[y][1]==x),q=p^1;
    if (y==rot) rot=x;
        else if (c[z][0]==y) c[z][0]=x; else c[z][1]=x;
    fa[x]=z; fa[y]=x; fa[c[x][q]]=y;
    c[y][p]=c[x][q]; c[x][q]=y; 
}

inline void splay(int x,int &rot)
{
    while (x!=rot)
    {
        int y=fa[x],z=fa[y];
        if (y!=rot)
            if ((c[y][0]==x)xor(c[z][0]==y)) zigzag(x,rot); else zigzag(y,rot);
        zigzag(x,rot);
    }
}

inline void insert(int &t,int anc,int now)//加入平衡树
{
    if (t==0)
    {
        t=now;
        fa[t]=anc;
        return ;
    }
    if (x[now]<=x[t]+eps) insert(c[t][0],t,now);
    else insert(c[t][1],t,now);
}

inline double getk(int i,int j)//求斜率
{
    if (fabs(x[i]-x[j])<eps) return -inf;
    else return (y[j]-y[i])/(x[j]-x[i]);
}

inline int prev(int rot)//求可以和当前点组成凸包的右边第一个点
{
    int t=c[rot][0],tmp=t;
    while (t)
    {
        if (getk(t,rot)<=lk[t]+eps) tmp=t,t=c[t][1];
        else t=c[t][0];
    }
    return tmp;
}
inline int succ(int rot)//求可以和当前点组成凸包的左边第一个点
{
    int t=c[rot][1],tmp=t;
    while (t)
    {
        if (getk(rot,t)+eps>=rk[t]) tmp=t,t=c[t][0];
        else t=c[t][1];
    }
    return tmp;
}

inline void update(int t)//加入t点
{
    splay(t,rot);
    if (c[t][0])//向左求凸包
    {
        int left=prev(rot);
        splay(left,c[rot][0]); c[left][1]=0;
        lk[t]=rk[left]=getk(left,t);
    }
    else lk[t]=inf;
    if (c[t][1])//向右求凸包
    {
        int right=succ(rot);
        splay(right,c[rot][1]); c[right][0]=0;
        rk[t]=lk[right]=getk(t,right);
    }
    else rk[t]=-inf;
    if (lk[t]<=rk[t]+eps)//在原凸包内部的情况,直接删掉该点 
    {
        rot=c[t][0]; c[rot][1]=c[t][1]; fa[c[t][1]]=rot; fa[rot]=0;
        lk[rot]=rk[c[t][1]]=getk(rot,c[t][1]);
    }
}        

inline int find(int t,double k)//找到当前斜率的位置,即找到最优值
{
    if (t==0) return 0;
    if (lk[t]+eps>=k&&k+eps>=rk[t]) return t;
    if (k+eps>lk[t]) return find(c[t][0],k);
    else return find(c[t][1],k);
}

int main()
{
    //freopen("cash.in","r",stdin);
    //freopen("cash.out","w",stdout);
    scanf("%d%lf",&n,&f[0]);
    for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&a[i],&b[i],&rate[i]);
    for (int i=1;i<=n;i++)
    {
        int j=find(rot,-a[i]/b[i]);
        f[i]=max(f[i-1],x[j]*a[i]+y[j]*b[i]);
        y[i]=f[i]/(a[i]*rate[i]+b[i]);
        x[i]=y[i]*rate[i];
        insert(rot,0,i);
        update(i);
    }
    printf("%.3lf\n",f[n]);
    return 0;
}
View Code

cdq分治代码:

#include <iostream>
#include <string.h>
#include <string>
#include <stdio.h>
#include <algorithm>
#include <math.h>
#define maxn 120000
#define eps 1e-9
#define inf 1e9
using namespace std;

int n; double f[maxn];
int s[maxn];

struct query {
    double q, a, b, rate, k;
    int pos;
    bool operator < (const query &rhs) const{
        return k < rhs.k;
    }
}q[maxn], nq[maxn];

struct point {
    double x, y;
    friend bool operator < (const point &a, const point &b) {
        if (a.x < b.x + eps) return true;
        if (fabs(a.x - b.x) <= eps && a.y - b.y < eps) return true;
        return false;
    }
}p[maxn], np[maxn];

double getk(int i, int j) {
    if (i == 0) return -inf;
    if (j == 0) return inf; 
    if (fabs(p[i].x - p[j].x) <= eps) return -inf;
    return (p[i].y - p[j].y) / (p[i].x - p[j].x);
}

void cdq(int l, int r) {
    if (l == r) {
        f[r] = max(f[r - 1], f[r]);
        p[r].y = f[r] / (q[r].a*q[r].rate + q[r].b);
        p[r].x = p[r].y*q[r].rate;
        return;
    }
    int mid = (l + r) >> 1, tl = l, tr = mid+1;
    for (int i = l; i <= r; ++i) {
        if (q[i].pos <= mid) nq[tl++] = q[i];
        else nq[tr++] = q[i];
    }
    for (int i = l; i <= r; ++i) q[i] = nq[i];
    cdq(l, mid);
    int top = 0;
    for (int i = l; i <= mid; ++i) {
        while (top >= 2 && getk(i, s[top]) + eps > getk(s[top], s[top - 1])) --top;
        s[++top] = i;
    }
    int j = 1;
    for (int i = r; i >= mid + 1; --i) {
        while (j < top && q[i].k < getk(s[j], s[j+1]) + eps) ++j;
        f[q[i].pos] = max(f[q[i].pos], p[s[j]].x*q[i].a + p[s[j]].y*q[i].b);
    }
    cdq(mid + 1, r);
    int ll = l, lr = mid + 1;
    for (int i = l; i <= r; ++i) {
        if ((p[ll] < p[lr] || lr > r) && ll <= mid) np[i] = p[ll++];
        else np[i] = p[lr++];
    }
    for (int i = l; i <= r; ++i) p[i] = np[i];
}

int main() {
    scanf("%d%lf", &n, &f[0]);
    for (int i = 1; i <= n; ++i) {
        scanf("%lf%lf%lf", &q[i].a, &q[i].b, &q[i].rate);
        q[i].pos = i; q[i].k = -q[i].a / q[i].b;
    }
    sort(q + 1, q + 1 + n); 
    cdq(1, n);
    printf("%.3lf\n", f[n]);
    return 0;
}
View Code
posted @ 2017-02-20 21:25  BOSON+CODE  阅读(232)  评论(0编辑  收藏  举报