[BZOJ1492][NOI2007]货币兑换Cash

[NOI2007]货币兑换Cash

Description
这里写图片描述

Input
第一行两个正整数N、S,分别表示小Y 能预知的天数以及初始时拥有的钱数。 接下来N 行,第K 行三个实数AK、BK、RateK,意义如题目中所述
Output
只有一个实数MaxProfit,表示第N 天的操作结束时能够获得的最大的金钱 数目。答案保留3 位小数。
Sample Input
3 100
1 1 1
1 2 2
2 2 3
Sample Output
225.000
HINT

这里写图片描述

测试数据设计使得精度误差不会超过10-7。
对于40%的测试数据,满足N ≤ 10;
对于60%的测试数据,满足N ≤ 1 000;
对于100%的测试数据,满足N ≤ 100 000;

Solution:
首先我们列出 dp 方程
dp[i]= max{ f[j]B[j]+A[j]Rate[j](Rate[j]A[i]+B[i]) }
dp[0]=S
这是一个典型的 1D/1Ddp
具体推导如下
y[j]=f[j]B[j]+A[j]Rate[j],x[j]=y[j]Rate[j]

dp[i]=max { x[j]A[i]+y[j]B[i] }
这是典型的斜率优化形式,斜率和横坐标都不单调,因此我们需要 splay
【吐槽:bzoj的数据太弱了,你们可以看到我的 maintain 函数其实只考虑了把新插进来的决策左边拉成凸壳,右边拉成凸壳,而如果我的新决策点在原凸壳里的话这样做是不对的,应该先判断点是否在凸壳内部的,我也不知道为什么这样就对了,大概是新点都在壳外面吧 = =

Code

#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define per(i, r, l) for (int i = (r); i >= (l); i--)
#define MS(_) memset(_, 0, sizeof(_))
#define PB push_back
#define MP make_pair
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long ll;
template<typename T> inline void read(T &x){
    x = 0; T f = 1; char ch = getchar();
    while (!isdigit(ch)) {if (ch == '-') f = -1; ch = getchar();}
    while (isdigit(ch))  {x = x * 10 + ch - '0'; ch = getchar();}
    x *= f;
}
const double INF = 1e9;
const double eps = 1e-9;
const int N = 111111;
int n;
double x[N], y[N], dp[N], a[N], b[N], rate[N], k[2][N];
//Geometry
inline double calk(int a, int b){
    if (abs(x[a]-x[b])<eps) return -INF;
    return (y[a]-y[b])/(x[a]-x[b]); 
}
inline bool gt(double a, double b) {return a-b > eps;}
inline bool ge(double a, double b) {return a-b >= eps;}
inline bool le(double a, double b) {return a-b <= eps;}
inline bool check(double a, double b, int d){ return d ? ge(a, b) : le(a, b); }
//Splay
struct Node{
    Node *p, *ch[2]; int num;
    inline int d(){return this == p->ch[1]; }
    inline void sc(Node *_c, int _d) {ch[_d] = _c; if (_c) _c->p = this;}
}*root, mem[N], *pos[N], *s[N]; int top;
inline void init_memory(){REP(i, N) s[i] = mem+i; top = N;}
inline void build(){init_memory(); root = 0;}
inline Node *new_node() {return s[--top];}
inline Node *new_node(int v){
    Node *ret = new_node();
    ret->num = v; ret->ch[0] = ret->ch[1] = ret->p = 0;
    return ret;
}
inline void rot(Node *t){
    Node *p = t->p; int d = t->d();
    p->sc(t->ch[!d], d); 
    if (p->p) p->p->sc(t, p->d()); else t->p = 0; 
    t->sc(p, !d); 
    if (p == root) root = t;
}
inline void splay(Node *t, Node *fa = 0){
    for (; t->p != fa; ){
        if (t->p->p == fa) rot(t);
        else t->d() == t->p->d() ? (rot(t->p), rot(t)) : (rot(t), (rot(t)));
    }
}
inline int find(double nk){
    Node *rt = root;
    while (rt){
        if (ge(k[0][rt->num], nk) && ge(nk, k[1][rt->num])) return rt->num;
        rt = rt->ch[!gt(nk, k[0][rt->num])];
    }
    return 0;
}
inline void insert(Node *fa, Node *&rt, int num){
    if (!rt) {pos[num] = rt = new_node(num); rt->p = fa; return;}
    if (le(x[num], x[rt->num])) insert(rt, rt->ch[0], num);
    else insert(rt, rt->ch[1], num);
}
inline Node *fix(int d){ Node *p, *res;
    for (res = p = root->ch[d]; p;){
        bool _d = check(calk(p->num, root->num), k[d][p->num], d);
        if (_d){res = p; p = p->ch[!d];} else p = p->ch[d];
    }
    return res;
}
inline void maintain(int num){
    Node *rt = pos[num]; splay(pos[num]);
    rep(i, 0, 1) 
        if (rt->ch[i]){ 
        Node *p = fix(i);
        k[!i][p->num] = k[i][num] = calk(p->num, num);
        splay(p, root); p->ch[!i] = 0;
    }
    if (!rt->ch[0]) k[0][num] = INF;
    if (!rt->ch[1]) k[1][num] = -INF;
}
int main(){
    read(n); scanf("%lf", &dp[0]); build();
    rep(i, 1, n) scanf("%lf%lf%lf", &a[i], &b[i], &rate[i]);
    rep(i, 1, n){
        int p = find(-a[i]/b[i]);
        dp[i] = max(dp[i-1], x[p]*a[i]+y[p]*b[i]);
        y[i] = dp[i] / (a[i] * rate[i] + b[i]);
        x[i] = y[i] * rate[i]; 
        insert(0, root, i);
        maintain(i);
    }
    printf("%.3lf\n",dp[n]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值