【cdq分治】[Noi2007] bzoj1492 货币兑换Cash
2015-04-04 15:30
405 查看
题目点这里
嗯。。倒腾了这么久终于折腾出来了。。
对cdq有一点感觉了T_T当然。。还是很多不懂 = =
在雅礼写cdq写不出来的感觉实在是。。。。
首先这题有一个贪心 就是每天的买卖一定是100% 这样才能保证收益最大
然后设x[i]表示第i天最多拥有A劵的数量 y[i]表示第i天最多拥有B劵的数量 f[i]表示第i天把劵全卖出去获得的最大金钱数
那么 f[i] = max{ f[i -1], x[j] * A[i] + y[j] * B[i] } (j < i)
其中 x[j] = f[j] / (A[j] * rate[j] + B[j]) y[j] = x[j] * rate[j]
f[i -1]更新是指不卖也不买。。这个很好更新先不考虑
两边同时除以B[i](常数) f[i] / B[i] = A[i] / B[i] * x[i] + y[i] 即 y[i] = -A[i] / B[i] * x[i] + f[i] / B[i]
令 k = -A[i] / B[i] 维护上凸壳即可
由于斜率不是单调的 所以我们需要一颗叫splay的植物来维护 _(:з)∠)_
据说很容易写挂。。(没写捂脸)然后神奇的陈丹琪研究出了神奇的分治做法。。
定义solve(L, R)为回答[L, R]区间的询问 认为处理完以后我们就能得到[L, R]中的x[i]和y[i] 并且已按照x为第一关键字y为第二关键字排好序
二分出Mid 首先对于[L, Mid]递归处理 那么现在用[L, Mid]中的信息来更新[Mid + 1, R]这段区间的f[i]
此时的斜率依旧不是单调的 但是我们可以先把斜率sort一下那么就能直接用单调队列O(N)更新了
也就是说 对于每个区间 用左区间构造一次上凸壳 更新一遍右边排好序的斜率
然后再递归处理[Mid + 1, R] 最后把左右区间按照之前定义所说的顺序排好序(这一步是一个归并过程 复杂度O(N))
这样的做法 分治的复杂度是O(N(logN)^2)的 因为每次在更新的时候还要排序一次斜率
实际上我们可以预先把斜率排序好 然后每次需要递归左区间的时候把原本在左区间的数拣出来 最后合起来
开始归并写挂了 = =一直查不出错。。还有其实可以少写一个结构体的 我只是为了方便排序把d和p分开写了
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int Nmax = 1e5 + 5;
const double eps = 1e-9;
const double inf = 1e20;
int N;
double f[Nmax];
struct Days{
int pos;
double a, b, rate, k;
bool operator < (const Days &_) const {
return k < _.k;
}
}d[Nmax], td[Nmax];
inline void update(double &a, double b) { if (b > a) a = b; }
inline double Fabs(double a) { return a > 0 ? a : -a; }
struct Point{
double x, y;
bool operator < (const Point &_) const {
return x < _.x || (Fabs(x - _.x) < eps && y < _.y);
}
}p[Nmax], tp[Nmax];
struct queue{
int num[Nmax], head, tail;
void init() { head = 0; tail = -1; num[0] = 0; }
void push_back(int a){ num[++tail] = a; }
void pop_back() { --tail; }
void pop_front() { ++head; }
int front() { return num[head]; }
int second() { return num[head + 1]; }
int back() { return num[tail]; }
int before() { return num[tail - 1]; }
int size() { return tail - head + 1; }
}q;
inline double getk(int i, int j)
{
if (!j) return inf;
if (!i || Fabs(p[i].x - p[j].x) < eps) return -inf;
return (p[i].y - p[j].y) / (p[i].x - p[j].x);
}
inline void split(int L, int M, int R)
{
int L1 = L, L2 = M + 1;
for (int i = L; i <= R; ++i) {
if (d[i].pos <= M) td[L1++] = d[i];
else td[L2++] = d[i];
}
for (int i = L; i <= R; ++i) d[i] = td[i];
}
inline void merge(int L, int M, int R)
{
int L1 = L, L2 = M + 1;
for (int i = L; i <= R; ++i) {
if ((p[L1] < p[L2] || L2 > R) && L1 <= M) tp[i] = p[L1++];
else tp[i] = p[L2++];
}
for (int i = L; i <= R; ++i) p[i] = tp[i];
}
void solve(int L, int R)
{
if (L == R) {
update(f[L], f[L - 1]);
p[L].y = f[L] / (d[L].a * d[L].rate + d[L].b);
p[L].x = p[L].y * d[L].rate;
return;
}
int M = (L + R) >> 1;
split(L, M, R); solve(L, M);
q.init();
for (int i = L; i <= M; ++i) {
while (q.size() > 1 && getk(q.before(), q.back()) <= getk(q.back(), i)) q.pop_back();
q.push_back(i);
}
for (int i = R; i > M; --i) {
while (q.size() > 1 && d[i].k <= getk(q.front(), q.second())) q.pop_front();
update(f[d[i].pos], p[q.front()].x * d[i].a + p[q.front()].y * d[i].b);
}
solve(M + 1, R); merge(L, M, R);
}
int main()
{
ios :: sync_with_stdio(false);
cin >> N >> f[0];
for (int i = 1; i <= N; ++i) {
cin >> d[i].a >> d[i].b >> d[i].rate;
d[i].k = -d[i].a / d[i].b; d[i].pos = i;
}
sort(d + 1, d + N + 1);
solve(1, N); printf("%.3f\n", f
);
return 0;
}
嗯。。倒腾了这么久终于折腾出来了。。
对cdq有一点感觉了T_T当然。。还是很多不懂 = =
在雅礼写cdq写不出来的感觉实在是。。。。
首先这题有一个贪心 就是每天的买卖一定是100% 这样才能保证收益最大
然后设x[i]表示第i天最多拥有A劵的数量 y[i]表示第i天最多拥有B劵的数量 f[i]表示第i天把劵全卖出去获得的最大金钱数
那么 f[i] = max{ f[i -1], x[j] * A[i] + y[j] * B[i] } (j < i)
其中 x[j] = f[j] / (A[j] * rate[j] + B[j]) y[j] = x[j] * rate[j]
f[i -1]更新是指不卖也不买。。这个很好更新先不考虑
两边同时除以B[i](常数) f[i] / B[i] = A[i] / B[i] * x[i] + y[i] 即 y[i] = -A[i] / B[i] * x[i] + f[i] / B[i]
令 k = -A[i] / B[i] 维护上凸壳即可
由于斜率不是单调的 所以我们需要一颗叫splay的植物来维护 _(:з)∠)_
据说很容易写挂。。(没写捂脸)然后神奇的陈丹琪研究出了神奇的分治做法。。
定义solve(L, R)为回答[L, R]区间的询问 认为处理完以后我们就能得到[L, R]中的x[i]和y[i] 并且已按照x为第一关键字y为第二关键字排好序
二分出Mid 首先对于[L, Mid]递归处理 那么现在用[L, Mid]中的信息来更新[Mid + 1, R]这段区间的f[i]
此时的斜率依旧不是单调的 但是我们可以先把斜率sort一下那么就能直接用单调队列O(N)更新了
也就是说 对于每个区间 用左区间构造一次上凸壳 更新一遍右边排好序的斜率
然后再递归处理[Mid + 1, R] 最后把左右区间按照之前定义所说的顺序排好序(这一步是一个归并过程 复杂度O(N))
这样的做法 分治的复杂度是O(N(logN)^2)的 因为每次在更新的时候还要排序一次斜率
实际上我们可以预先把斜率排序好 然后每次需要递归左区间的时候把原本在左区间的数拣出来 最后合起来
开始归并写挂了 = =一直查不出错。。还有其实可以少写一个结构体的 我只是为了方便排序把d和p分开写了
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int Nmax = 1e5 + 5;
const double eps = 1e-9;
const double inf = 1e20;
int N;
double f[Nmax];
struct Days{
int pos;
double a, b, rate, k;
bool operator < (const Days &_) const {
return k < _.k;
}
}d[Nmax], td[Nmax];
inline void update(double &a, double b) { if (b > a) a = b; }
inline double Fabs(double a) { return a > 0 ? a : -a; }
struct Point{
double x, y;
bool operator < (const Point &_) const {
return x < _.x || (Fabs(x - _.x) < eps && y < _.y);
}
}p[Nmax], tp[Nmax];
struct queue{
int num[Nmax], head, tail;
void init() { head = 0; tail = -1; num[0] = 0; }
void push_back(int a){ num[++tail] = a; }
void pop_back() { --tail; }
void pop_front() { ++head; }
int front() { return num[head]; }
int second() { return num[head + 1]; }
int back() { return num[tail]; }
int before() { return num[tail - 1]; }
int size() { return tail - head + 1; }
}q;
inline double getk(int i, int j)
{
if (!j) return inf;
if (!i || Fabs(p[i].x - p[j].x) < eps) return -inf;
return (p[i].y - p[j].y) / (p[i].x - p[j].x);
}
inline void split(int L, int M, int R)
{
int L1 = L, L2 = M + 1;
for (int i = L; i <= R; ++i) {
if (d[i].pos <= M) td[L1++] = d[i];
else td[L2++] = d[i];
}
for (int i = L; i <= R; ++i) d[i] = td[i];
}
inline void merge(int L, int M, int R)
{
int L1 = L, L2 = M + 1;
for (int i = L; i <= R; ++i) {
if ((p[L1] < p[L2] || L2 > R) && L1 <= M) tp[i] = p[L1++];
else tp[i] = p[L2++];
}
for (int i = L; i <= R; ++i) p[i] = tp[i];
}
void solve(int L, int R)
{
if (L == R) {
update(f[L], f[L - 1]);
p[L].y = f[L] / (d[L].a * d[L].rate + d[L].b);
p[L].x = p[L].y * d[L].rate;
return;
}
int M = (L + R) >> 1;
split(L, M, R); solve(L, M);
q.init();
for (int i = L; i <= M; ++i) {
while (q.size() > 1 && getk(q.before(), q.back()) <= getk(q.back(), i)) q.pop_back();
q.push_back(i);
}
for (int i = R; i > M; --i) {
while (q.size() > 1 && d[i].k <= getk(q.front(), q.second())) q.pop_front();
update(f[d[i].pos], p[q.front()].x * d[i].a + p[q.front()].y * d[i].b);
}
solve(M + 1, R); merge(L, M, R);
}
int main()
{
ios :: sync_with_stdio(false);
cin >> N >> f[0];
for (int i = 1; i <= N; ++i) {
cin >> d[i].a >> d[i].b >> d[i].rate;
d[i].k = -d[i].a / d[i].b; d[i].pos = i;
}
sort(d + 1, d + N + 1);
solve(1, N); printf("%.3f\n", f
);
return 0;
}
相关文章推荐
- bzoj1492 [NOI2007]货币兑换Cash【cdq分治】
- [BZOJ1492][NOI2007]货币兑换Cash && CDQ分治+斜率优化
- BZOJ-1492-货币兑换cash-NOI2007-CDQ分治
- [BZOJ1492][NOI2007]货币兑换Cash(斜率优化dp+splay|cdq分治维护凸包)
- BZOJ_1492_[NOI2007]货币兑换Cash_CDQ分治+斜率优化
- [bzoj1492][cdq分治][斜率优化][NOI2007]货币兑换Cash
- 【BZOJ1492】[NOI2007]货币兑换Cash 斜率优化+cdq分治
- [BZOJ1492][NOI2007][CDQ分治][斜率优化][DP]货币兑换Cash
- [DP 斜率优化 CDQ分治||动态维护凸包] BZOJ 1492 [NOI2007]货币兑换Cash
- [BZOJ 1492][NOI2007]货币兑换Cash:CDQ分治|DP斜率优化
- [BZOJ1492]-[NOI2007]货币兑换Cash-斜率优化+CDQ
- bzoj-1492 货币兑换Cash (2)——CDQ分治
- 斜率优化(CDQ分治,Splay平衡树):BZOJ 1492: [NOI2007]货币兑换Cash
- NOI 2007 货币兑换Cash (bzoj 1492) - 斜率优化 - 动态规划 - CDQ分治
- 【BZOJ1492】【NOI2007】货币兑换(动态规划,CDQ分治,Splay)
- BZOJ1492: [NOI2007]货币兑换Cash 【dp + CDQ分治】
- bzoj [NOI2007]货币兑换Cash (cdq分治+斜率优化 )
- 【BZOJ】1492: [NOI2007]货币兑换Cash(cdq分治)
- 【BZOJ1492】【NOI2007】货币兑换(动态规划,CDQ分治,Splay)
- BZOJ1492: [NOI2007]货币兑换Cash 【dp + CDQ分治】