Bzoj1492: [NOI2007]货币兑换Cash(不单调的斜率优化)

题面

传送门

Sol

题目都说了

必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。

\(f[i]\)表示第\(i\)天可以有的最大钱数
枚举\(j<i\)在第\(j\)天用完所有的钱买劵
然后在第\(i\)天卖光
获得\(60\)

核心代码如下

for(RG int i = 1; i <= n; ++i){
        f[i] = f[i - 1];
        for(RG int j = 0; j < n; ++j) f[i] = max(f[i], A[j] * a[i] + B[j] * b[i]);
        B[i] = f[i] / (a[i] * rate[i] + b[i]), A[i] = B[i] * rate[i];
    }

优化

接着就来推柿子了
\(X[i]=A[i]\)\(Y[i]=B[i]\),这里的\(A, B\)指的是上面代码里的\(A, B\)
考虑斜率优化

\[b_iY_j+a_iX_j>b_iY_k+a_iX_k\]
假设\(k<j\)\(X_k<X_j\)

\[-\frac{a_i}{b_i}>\frac{Y_j-Y_k}{X_j-X_k}\]
\(K_i=\frac{a_i}{b_i}\)

\[K_i>\frac{Y_j-Y_k}{X_j-X_k}\]

然后你会发现如果用单调队列来维护斜率优化的话,显然是不行的
因为\(K\)\(X\)都是不单调的,所以显然不能单调队列

1.CDQ分治

竟然\(K\)\(X\)不单调
那么我们让它们单调

\(CDQ\)分治每次都是考虑左边对右边的贡献
那么我们可以把左边的\(X\)递增,排个序
把右边的\(K\)递减,也排个序
那么左边用单调队列,右边就直接可以查询了

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
const int _(1e5 + 5);
const double EPS(1e-7);
const double INF(2e9);
typedef long long ll;

int n, Q[_];
double a[_], b[_], r[_], f[_];
struct Calc{
    int id;
    double k, x, y;

    IL void Update(){
        y = f[id] / (a[id] * r[id] + b[id]), x = y * r[id];
    }
} p[_], q[_];

IL int Cmp(RG Calc A, RG Calc B){
    return A.k > B.k;
}

IL double Slope(RG int i, RG int j){
    if(q[j].x - q[i].x < EPS) return -INF;
    return (q[i].y - q[j].y) / (q[i].x - q[j].x);
}

IL void CDQ(RG int l, RG int r){
    if(l == r){
        f[l] = max(f[l], f[l - 1]), q[l].Update();
        return;
    }
    RG int mid = (l + r) >> 1, be = l, en = mid + 1, head = 1, tail = 0;
    for(RG int i = l; i <= r; ++i)
        if(q[i].id <= mid) p[be++] = q[i];
        else p[en++] = q[i];
    for(RG int i = l; i <= r; ++i) q[i] = p[i];
    CDQ(l, mid);
    for(RG int i = l; i <= mid; ++i){
        while(head < tail && Slope(Q[tail - 1], Q[tail]) < Slope(Q[tail - 1], i)) --tail;
        Q[++tail] = i;
    }
    for(RG int i = mid + 1; i <= r; ++i){
        while(head < tail && Slope(Q[head], Q[head + 1]) > q[i].k) ++head;
        f[q[i].id] = max(f[q[i].id], a[q[i].id] * q[Q[head]].x + b[q[i].id] * q[Q[head]].y);
    }
    CDQ(mid + 1, r);
    for(RG int i = l, j = mid + 1, k = l; k <= r; ++k)
        if(j > r || (i <= mid && q[i].x <= q[j].x)) p[k] = q[i++];
        else p[k] = q[j++];
    for(RG int i = l; i <= r; ++i) q[i] = p[i];
}

int main(RG int argc, RG char* argv[]){
    scanf("%d%lf", &n, &f[0]);
    for(RG int i = 1; i <= n; ++i){
        scanf("%lf%lf%lf", &a[i], &b[i], &r[i]);
        q[i].id = i, q[i].k = -a[i] / b[i];
    }
    sort(q + 1, q + n + 1, Cmp);
    CDQ(1, n);
    printf("%.3lf\n", f[n]);
    return 0;
}

2.Splay动态维护凸包

如果\(X\)单调,\(K\)不单调
那么是可以在单调队列上二分从而找到\(K\)的决策点的

然而\(X\)不单调,也就是说我们要在已有的凸包中间加入一个点
这个时候可以考虑\(Splay\)

\(Splay\)的关键字就是\(X\)
维护凸包上每个点左边直线的斜率和右边直线的斜率
过程:

  1. 判断如果新加的这个点在凸包内部,不管它
  2. 首先直接插入\(X\)并且\(Splay\)到根
  3. 然后在它的左子树上二分找到它能接到那个点的后面形成凸包
  4. 删掉那个点的右子树
  5. 右边同样找到可以接到它后面的点使得能形成凸包
  6. 删掉那个点的左子树
  7. 注意每次删除的时候都要维护这个点左边直线的斜率和右边直线的斜率

第一步判断的过程不好弄所以可以把它放在第\(6\)步后
显然这个点如果在凸包内部不会执行\(4, 6\)操作

查询就是在树上二分满足\(K\)要求的位置

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
const int _(1e5 + 5);
const double EPS(1e-7);
const double INF(2e9);
typedef long long ll;

int n, fa[_], ch[2][_], rt;
double f, X[_], Y[_], lk[_], rk[_];

IL double Slope(RG int i, RG int j){
    if(X[i] - X[j] < EPS) return -INF;
    return (Y[i] - Y[j]) / (X[i] - X[j]);
}

IL int Son(RG int x){
    return ch[1][fa[x]] == x;
}

IL void Rotate(RG int x){
    RG int y = fa[x], z = fa[y], c = Son(x);
    if(z) ch[Son(y)][z] = x; fa[x] = z;
    ch[c][y] = ch[!c][x], fa[ch[c][y]] = y;
    ch[!c][x] = y, fa[y] = x;
}

IL void Splay(RG int x, RG int ff){
    for(RG int y = fa[x]; y != ff; Rotate(x), y = fa[x])
        if(fa[y] != ff) Son(x) ^ Son(y) ? Rotate(x) : Rotate(y);
    if(!ff) rt = x;
}

IL int Query(RG int x, RG double k){
    if(!x) return 0;
    if(lk[x] >= k && rk[x] <= k) return x;
    if(lk[x] < k) return Query(ch[0][x], k);
    return Query(ch[1][x], k);
}

IL void Insert(RG int &x, RG int ff, RG int p){
    if(!x){
        x = p, fa[x] = ff;
        Splay(x, 0);
        return;
    }
    if(X[p] - X[x] < EPS) Insert(ch[0][x], x, p);
    else Insert(ch[1][x], x, p);
}

IL int Pre(RG int x){
    RG int y = ch[0][x], ret = y;
    while(y){
        if(lk[y] > Slope(x, y)) ret = y, y = ch[1][y];
        else y = ch[0][y];
    }
    Splay(ret, x);
    return ret;
}

IL int Suf(RG int x){
    RG int y = ch[1][x], ret = y;
    while(y){
        if(rk[y] < Slope(y, x)) ret = y, y = ch[0][y];
        else y = ch[1][y];
    }
    Splay(ret, x);
    return ret;
}

IL void Update(RG int x){
    if(ch[0][x]){
        RG int p = Pre(x); fa[ch[1][p]] = ch[1][p] = 0;
        rk[p] = lk[x] = Slope(x, p);
    }
    else lk[x] = INF;
    if(ch[1][x]){
        RG int p = Suf(x); fa[ch[0][p]] = ch[0][p] = 0;
        lk[p] = rk[x] = Slope(p, x);
    }
    else rk[x] = -INF;
    if(lk[x] <= rk[x]){
        fa[rt = ch[0][x]] = 0, fa[ch[1][rt] = ch[1][x]] = rt;
        rk[rt] = lk[ch[1][x]] = Slope(ch[1][x], rt);
    }
}

int main(RG int argc, RG char* argv[]){
    scanf("%d%lf", &n, &f);
    for(RG int i = 1; i <= n; ++i){
        RG double a, b, r;
        scanf("%lf%lf%lf", &a, &b, &r);
        RG int j = Query(rt, -a / b);
        f = max(f, X[j] * a + Y[j] * b);
        Y[i] = f / (a * r + b), X[i] = Y[i] * r;
        Insert(rt, 0, i), Update(rt);
    }
    printf("%.3lf\n", f);
    return 0;
}

转载于:https://www.cnblogs.com/cjoieryl/p/8717006.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值