bzoj1492 [NOI2007]货币兑换Cash

时间:2022-12-16 15:08:17

1492: [NOI2007]货币兑换Cash

Time Limit: 5 Sec  Memory Limit: 64 MB
Submit: 5840  Solved: 2346
[Submit][Status][Discuss]

Description

小Y最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下
简称B券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。每天随着市场的起伏波动,
两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 K 天中 A券 和 B券 的
价值分别为 AK 和 BK(元/单位金券)。为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法
。比例交易法分为两个方面:(a)卖出金券:顾客提供一个 [0,100] 内的实数 OP 作为卖出比例,其意义为:将
 OP% 的 A券和 OP% 的 B券 以当时的价值兑换为人民币;(b)买入金券:顾客支付 IP 元人民币,交易所将会兑
换给用户总价值为 IP 的金券,并且,满足提供给顾客的A券和B券的比例在第 K 天恰好为 RateK;例如,假定接
下来 3 天内的 Ak、Bk、RateK 的变化分别为:
bzoj1492 [NOI2007]货币兑换Cash
假定在第一天时,用户手中有 100元 人民币但是没有任何金券。用户可以执行以下的操作:
bzoj1492 [NOI2007]货币兑换Cash
注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经
知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能
够获得多少元钱。

Input

输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、B
K、RateK,意义如题目中所述。对于100%的测试数据,满足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤1
0^9。 n ≤ 10^5
【提示】
1.输入文件可能很大,请采用快速的读入方式。
2.必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。

Output

只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。

Sample Input

3 100
1 1 1
1 2 2
2 2 3

Sample Output

225.000

HINT

bzoj1492 [NOI2007]货币兑换Cash

分析:斜率优化的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;
}