题面

传送门

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;
}

Bzoj1492: [NOI2007]货币兑换Cash(不单调的斜率优化)的更多相关文章

  1. BZOJ1492: [NOI2007]货币兑换Cash(CDQ分治,斜率优化动态规划)

    Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个 ...

  2. [BZOJ1492] [NOI2007] 货币兑换Cash(cdq分治+斜率优化)

    [BZOJ1492] [NOI2007] 货币兑换Cash(cdq分治+斜率优化) 题面 分析 dp方程推导 显然,必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币:每次卖出操作卖出所有 ...

  3. [BZOJ1492][NOI2007]货币兑换Cash(斜率优化+CDQ分治)

    1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 5838  Solved: 2345[Submit][Sta ...

  4. bzoj1492[NOI2007]货币兑换Cash cdq分治+斜率优化dp

    1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 5541  Solved: 2228[Submit][Sta ...

  5. [BZOJ1492] [NOI2007]货币兑换Cash 斜率优化+cdq/平衡树维护凸包

    1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 5907  Solved: 2377[Submit][Sta ...

  6. bzoj千题计划237:bzoj1492: [NOI2007]货币兑换Cash

    http://www.lydsy.com/JudgeOnline/problem.php?id=1492 dp[i] 表示 第i天卖完的最大收益 朴素的dp: 枚举从哪一天买来的在第i天卖掉,或者是不 ...

  7. BZOJ1492: [NOI2007]货币兑换Cash 【dp + CDQ分治】

    1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MB Submit: 5391  Solved: 2181 [Submit][S ...

  8. cdq分治入门--BZOJ1492: [NOI2007]货币兑换Cash

    n<=100000天,一开始有s块钱,每天股票A价格ai,B价格bi,每天可以做的事情:卖出股票:按A:B=RTi的比例买入股票.问最后的最大收益.股票可以为浮点数,答案保留三位. 用脚指头想想 ...

  9. [NOI2007]货币兑换 「CDQ分治实现斜率优化」

    首先每次买卖一定是在某天 $k$ 以当时的最大收入买入,再到第 $i$ 天卖出,那么易得方程: $$f_i = \max \{\frac{A_iRate_kf_k}{A_kRate_k + B_k} ...

随机推荐

  1. 1. java 的访问修饰符

    一.什么情况下使用修饰符 属性通常使用private封装起来 方法一般使用public用于被调用 会被子类继承的方法,通常使用protected private protected package p ...

  2. 剑指offer——面试题19:正则表达式匹配

    #include"iostream" using namespace std; bool MatchCore(char*str,char* pattern); bool Match ...

  3. Oracle SQL Developer 查询时间格式

    工具->首选项->数据库->NLS->日期格式: DD-MON-RR 修改为: YYYY-MM-DD HH24:MI:SS

  4. java多线程-线程间协作

    大纲: wait.notify.notifyAll Condition 生产消费者 一.wait.notify.notifyAll wait.notify.notifyAll是Object的本地fin ...

  5. 文献综述十五:基于b/s中小型超市进销存管理系统的研究与设计

    一.基本信息 标题:基于b/s中小型超市进销存管理系统的研究与设计 时间:2015 出版源:湘西财经大学 文件分类:对超市管理系统的研究 二.研究背景 在竞争日益激烈的行业中,尽可能降低运营成本,逐步 ...

  6. vue element 常见问题

    1. vue2.0 给data对象新增属性,并触发视图更新  $set this.$set(this.ossData, "signature", 222) // 正确用法 // 数 ...

  7. 差分ADC到单端ADC

    单片机可以处理单端ADC(不在电压范围内要进行分压),也可以处理差分ADC(但需要双路输入).差分信号在传输过程中抗共模干扰能力很强,所以传输中都用差分传输,到ADC时可以差分也可以单端(需要放大器处 ...

  8. 再探display:table-cell &&左边固定、右边自适应

    display:table-cell;这个属性用的不多,它和td差不多,但是如果可以用好还是能收益不少的,下面举例说明. 1. 父元素display:table-cell,并设置verticle-al ...

  9. Oracle 锁机制

    本文参考自:ORACLE锁机制 1.oracle是一个多用户使用的共享资源,当多个用户并发的操作同一数据行时,那么在oracle数据库中就会存在多个事务操作统一数据行的操作,如果不对并发操作进行控制, ...

  10. js方法的使用(z)

    http://www.108js.com/article/article1/10025.html?id=58 javascript中正则匹配有3个方法,match,exec,test.这些方法都跟字符 ...