1492: [NOI2007]货币兑换Cash
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 5840 Solved: 2346
[Submit][Status][Discuss]
Description
Input
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
Sample Input
1 1 1
1 2 2
2 2 3
Sample Output
HINT
分析:斜率优化的boss题.
提示和样例说明需要仔细阅读,通过推(cai)导(ce),可以知道买入就一定会花光所有的钱,卖出就一定会卖出所有的券.这是第一步转化.
第二步转化比较难想到.既然每次买入和卖出都是全部的货币,那么钱就可以看作券嘛(脑补一下).假设第i天有f(i)的钱,那么可以将这些钱看作xi个A券和yi个B券. xi和yi的关系是知道的,它们的价值等于f(i)也是知道的,这就是一个二元一次方程组了.可以解出xi = f(i) * rate_i / (rate_i * Ai + Bi), yi = f(i) / (rate_i * Ai + Bi). 这样的话知道了钱数,每一天拥有多少A券和B券也就知道了,可以根据这个写出状态转移方程:
f(i) = max{f(i - 1),xj * Ai + yj * Bi}.
很可惜,这是O(n^2)的dp,不能通过此题.
想一想优化,这个很明显就是斜率优化的式子了.但是这个斜率优化和我之前做过的几道题都不太一样了:bzoj1911,bzoj3156,bzoj3437,bzoj3675,bzoj4518.这些题目都是只涉及到一个既有i又有j的式子,这里有两个:xj * Ai和yj * Bi,怎么办呢? 改写一下式子:yj = (-Ai / Bi) * xj + f(i) / Bi. 之前那些题都是只规定了横坐标的具体值,对于纵坐标没有具体的要求(要求的就是纵坐标),而这道题是要斜率为(-Ai / Bi)的直线经过点(xj,yj),使得截距f(i) / Bi最大. 因为Bi可以看作常量嘛,那么f(i)自然也就最大了.
将每个点(xi,yi)放到平面上,真正有影响的点是上凸包的点.维护上凸包,那么对于一条斜率为(-Ai / Bi)的直线,在凸包上找到两条相邻的直线,它们的斜率分别是k1,k2使得k1 ≤ (-Ai / Bi) ≤ k2.那么这两条直线的交点就是所求的点j,i可以从j转移过来.
怎么实现?每次插入一个点到凸包中,然后在凸包中查询,这是在维护一个动态凸包,为了强制其有序性,可以利用splay维护.
只是这个办法既麻烦又容易写错. 一个非常好的能够在有些情况替代高级数据结构的方法能派上用场:cdq分治!
cdq分治非常神奇,在你想到了用高级数据结构做一道题却觉得太麻烦太难写而不想写时,cdq分治或许恰好可以解决问题! cdq分治每次把序列分成两部分,那么对于左边一部分把凸包建出来,右边的点就能在左边的凸包中查找答案了. 维护上凸包有一个需要注意的地方:一定要先按照x从小到大排序! 建凸包很简单,右边的点的答案如何在左边的凸包中快速查询呢? 二分?慢了!
要知道,凸包是具有单调性的!维护的凸包是一个上凸包,斜率是慢慢减小的. 那么将右边的点按照斜率从大到小排序.在左边用一个指针找就可以了,这是O(n)的.
至此,这道题就被成功地解决了,有几个地方需要注意:
1.cdq分治一定要先分治左边,再来算左边对右边的贡献.
2.求凸包前一定要先排序!
3.求斜率时如果两个点的横坐标相等,返回-inf(斜率不存在).
4.指针记得清零(这一点让我TLE了好多次).
不得不说cdq分治是一个很好的方法,如果不想写高级数据结构要想一想cdq分治行不行. 凸包的单调性也要能想到.
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 100010;
const double eps = 1e-8;
int n,s,cnt1,cnt2,tot;
double f[maxn];
struct node
{
double x,y,a,b,rate,k;
int id;
}e[maxn],q1[maxn],q2[maxn],sta[maxn];
bool cmp1(node a,node b)
{
return a.x < b.x;
}
bool cmp2(node a,node b)
{
return a.k > b.k;
}
int three(double x)
{
if (fabs(x) < eps)
return 0;
if (x > 0)
return 1;
return -1;
}
double getK(node a,node b)
{
if (three(a.x - b.x) == 0)
return -1e18;
return (a.y - b.y) / (a.x - b.x);
}
void cdq(int l,int r)
{
if (l == r)
{
f[l] = max(f[l],f[l - 1]);
e[l].x = f[l] * e[l].rate / (e[l].rate * e[l].a + e[l].b);
e[l].y = f[l] / (e[l].rate * e[l].a + e[l].b);
return;
}
int mid = (l + r) >> 1;
cdq(l,mid);
cnt1 = cnt2 = 0;
for (int i = l; i <= mid; i++)
q1[++cnt1] = e[i];
for (int i = mid + 1; i <= r; i++)
q2[++cnt2] = e[i];
sort(q1 + 1,q1 + 1 + cnt1,cmp1);
sort(q2 + 1,q2 + 1 + cnt2,cmp2);
tot = 0;
for (int i = 1; i <= cnt1; i++)
{
while (tot > 1 && three(getK(q1[i],sta[tot]) - getK(sta[tot],sta[tot - 1])) >= 0)
tot--;
sta[++tot] = q1[i];
}
int now = 1;
for (int i = 1; i <= cnt2; i++)
{
while (now < tot && three(getK(sta[now + 1],sta[now]) - q2[i].k) >= 0)
now++;
node temp = sta[now];
f[q2[i].id] = max(f[q2[i].id],temp.x * q2[i].a + temp.y * q2[i].b);
}
cdq(mid + 1,r);
}
int main()
{
scanf("%d%lf",&n,&f[0]);
for (int i = 1; i <= n; i++)
{
scanf("%lf%lf%lf",&e[i].a,&e[i].b,&e[i].rate);
e[i].id = i;
e[i].k = -e[i].a / e[i].b;
}
cdq(1,n);
printf("%.3lf\n",f[n]);
return 0;
}