BZOJ题面是图片来的.
文字版题面
1797 货币兑换
2007年NOI全国竞赛
时间限制: 1 s
空间限制: 128000 KB
题目等级 : 大师 Master
题目描述 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的变化分别为:
时间 Ak Bk Ratek
第一天 1 1 1
第二天 1 2 2
第三天 2 2 3
假定在第一天时,用户手中有 100 元人民币但是没有任何金券。 用户可以执行以下的操作:
时间 用户操作 人民币(元) A 券的数量 B 券的数量
开户 无 100 0 0
第一天 买入100 元 0 50 50
第二天 卖出 50% 75 25 25
第二天 买入60 元 15 55 40
第三天 卖出 100% 205 0 0
注意到,同一天内可以进行多次操作。
小 Y 是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经 知道了未来 N 天内的 A 券和 B 券的价值以及 Rate。他还希望能够计算出来,如 果开始时拥有 S 元钱,那么 N 天后最多能够获得多少元钱。
输入描述 Input Description
第一行两个正整数 N、S,分别表示小 Y 能预知的天数以及初始时拥有的钱数。
接下来 N 行,第 K 行三个实数 AK、BK、RateK,意义如题目中所述。
输出描述 Output Description
只有一个实数 MaxProfit,表示第 N 天的操作结束时能够获得的最大的金钱 数目。答案保留 3 位小数。
样例输入 Sample Input
3 100
1 1 1
1 2 2
2 2 3
样例输出 Sample Output
225.000
数据范围及提示 Data Size & Hint
测试数据设计使得精度误差不会超过 10?7。
对于 40%的测试数据,满足 N ≤ 10;
对于 60%的测试数据,满足 N ≤ 1 000;
对于 100%的测试数据,满足 N ≤ 100 000;
对于 100%的测试数据,满足: 0 < AK ≤ 10; 0 < BK ≤ 10; 0 < RateK ≤ 100;MaxProfit ≤ 109;
样例说明
开户 无 100 0 0 第一天 买入 100 元 0 50 50 第二天 卖出 100% 150 0 0 第二天 买入 150 元 0 75 37.5 第三天 卖出 100% 225 0 0
时间 用户操作 人民币(元) A 券的数量 B 券的数量
开户 无 100 0 0
第一天 买入100元 0 50 50
第二天 卖出100% 150 0 0
第二天 买入150元 0 75 37.5
第三天 卖出100% 225 0 0
这只是个简单的cdq…真的…
然而斜率优化DP我还没有学过QAQ
这道题既练了cdq又学了斜率优化DP>ω<
在浮点数判断的时候无脑在两边加个1e-9导致某个点死活过不去
最后乱搞删掉了竟然过了卧槽(╯‵□′)╯︵┻━┻
速度倒不是很快甚至比某些Splay维护的还要慢…
不过毕竟好写而且A了不是吗.
(对着论文YY代码真SXBK)
先是暴力代码O(n2)很好写大家都会
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAXN 100100
using namespace std;
int n,s;
double a[MAXN],b[MAXN],rate[MAXN];
double ans;
double f[MAXN];
int main()
{
scanf("%d%d",&n,&s);
for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&a[i],&b[i],&rate[i]);
ans=s;
f[1]=s*rate[1]/(a[1]*rate[1]+b[1]);
for (int i=2;i<=n;i++)
{
for (int j=1;j<i;j++)
{
double x=f[j]*a[i]+f[j]/rate[j]*b[i];
ans=max(ans,x);
}
f[i]=ans*rate[i]/(a[i]*rate[i]+b[i]);
}
printf("%.3f",ans);
}
然后是AC做法(其实我是从论文看的要不我写个毛斜率优化):
由之前的DP方程:
如果在第i天,有两个决策j,k供我们选择,若决策j比决策k要优,那么有
(f[j]?f[k])?A[i]+(f[j]/Rate[j]?f[k]/Rate[k])?B[i]>0
其中f和暴力里意义一样,是第j天A最多数目
为了方便,我们设f[j]<f[k],g[j]=f[j]/Rate[j],g[k]=f[k]/Rate[k]也就是说g为某天与f对应的B的数目
那么原式就可以化简成斜率形式了:
(g[j]?g[k])/(f[j]?f[k])<?A[i]B[i]
这样看的话不就可以用斜率优化了吗~
所以2007年的NOI考场上,神犇们都开始动手码一棵棵平衡树来维护凸包斜率了>0<
还给不给蒟蒻+手残一点活路啦喂!
还好新世纪的我们有了cdq分治www
于是我们可以把每一天当成一个操作,以?A[i]B[i]为关键字构成操作序列然后cdq分治走起.
利用已经完成的操作序列去更新后面的答案.
既然已经知道斜率优化和cdq了,怎么写就很好办啦~我直接贴代码好了
//AC code by CreationAugust
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAXN 100100
#define MAXDBL 1e19+9
#define delta 1e-9
using namespace std;
struct Query
{
int id;
double a,b,rate;//a为当天A价格,b为当天B价格,rate为当天比例
double x,y;//维护点(x,y) 即(f[i],g[i]) f[i]是A券最多数目,g[i]是对应B券数目
double k;//价格构成的斜率,也是cdq排序的关键字
bool operator <(const Query& a)const{
return k>a.k;
}
}ques[MAXN],newq[MAXN];
double ans[MAXN];//ans是最优答案
int stack[MAXN];//建栈维护一个凸包的凸线
int n,s;
double slope(int a,int b)//a,b两点间斜率
{
if (!b) return -MAXDBL;
if (fabs(ques[a].x-ques[b].x)<delta) return MAXDBL;
return (ques[b].y-ques[a].y)/(ques[b].x-ques[a].x);
}
void solve(int l,int r)
{
int mid=(l+r)>>1,tp1=l,tp2=mid+1;
if (l==r)
{
ans[l]=max(ans[l],ans[l-1]);
ques[l].y=ans[l]/(ques[l].a*ques[l].rate+ques[l].b);
ques[l].x=ques[l].y*ques[l].rate;
return;
}
for (int i=l;i<=r;i++)//按时间划分操作序列
if (ques[i].id<=mid) newq[tp1++]=ques[i];
else newq[tp2++]=ques[i];
memcpy(ques+l,newq+l,sizeof(Query)*(r-l+1));
solve(l,mid);
int top=0,j=1;
for (int i=l;i<=mid;i++)
{
while (top>1&&(slope(stack[top-1],stack[top])-slope(stack[top-1],i)<delta)) top--;
stack[++top]=i;
}
stack[++top]=0;//注意要设定边界
for (int i=mid+1;i<=r;i++)
{
while (j<top&&ques[i].k-slope(stack[j],stack[j+1])<delta) j++;
ans[ques[i].id]=max(ans[ques[i].id],ques[stack[j]].x*ques[i].a+ques[stack[j]].y*ques[i].b);
}
solve(mid+1,r);
tp1=l,tp2=mid+1;
for (int i=l;i<=r;i++)
if ((((ques[tp1].x<ques[tp2].x)||(fabs(ques[tp1].x-ques[tp2].x)<delta&&ques[tp1].y<ques[tp2].y))||tp2>r)&&tp1<=mid) newq[i]=ques[tp1++];
else newq[i]=ques[tp2++];
memcpy(ques+l,newq+l,sizeof(Query)*(r-l+1));
}
int main()
{
scanf("%d%d",&n,&s);
ans[0]=s;
for (int i=1;i<=n;i++)
{
scanf("%lf%lf%lf",&ques[i].a,&ques[i].b,&ques[i].rate);
ques[i].id=i;ques[i].k=-ques[i].a/ques[i].b;
}
sort(ques+1,ques+n+1);
solve(1,n);
printf("%.3f",ans[n]);
}
代码不长,比平衡树和谐多了,跑的也不算很慢(虽然比某些神犇的Splay跑的还慢QAQ)