Time Limit:10000MS Memory Limit:65536K Description Input
Output
Sample Input
Sample Output
Hint Source xinyue |
题目:http://mail.bashu.cn:8080/bs_oj/showproblem?problem_id=2238
题意:已知n天里两种债券的价值,还有每天可以买入的比例,求到第n天最多能获得的钱
分析:这题算是个经典题吧,具体的题解到处都是,这里就简单说说。。。
我们很容易想到一个转移方程,f[ i ]为第i天能获得的最多钱数,那么有f[ i ]=max{ f[ j ]/(r[ j ]*a[ j ]+b[ j ])*r[ j ]*a[ i ]+f[ j ]/(r[ j ]*a[ j ]+b[ j ])*b[ i ]} 1<=j<i
这样转移的复杂度为O(n^2)
我们容易联想到斜率优化之类的,那么我们试着假设 x= f[ j ]/(r[ j ]*a[ j ]+b[ j ])*r[ j ], y= f[ j ]/(r[ j ]*a[ j ]+b[ j ]) ,G=a[ i ]*x +b[ i ]*y
那么有 y=-(a[i ]/b[i])*x+G/b[i] ,这个看起来可以用斜率优化来做,但是再仔细一看,x是无序的,y也是无序的,根本没办法用队列来维护一条凸线。。。
这时候splay之类的平衡树就派上用场了,我们可以用splay来维护这条上凸线 ^_^
具体先维护以x 大小为序的splay,对于每个节点,记录离他最近的左端点和右端点(待会维护凸线需要用到)
1.每次插入一个节点,先把它转为根节点,再往右边查找最近的满足凸线性质的节点(也就是 根->x->x最近的右端点向右拐),然后把它转到根节点下面,直接删掉其左子树。
往左边维护类似,由于这个新的节点可能不是凸线上的节点,再把它当前的左儿子转为根,做一次向右维护就行
2.每次查找值的话,先判断计算出离他最近的左右端点的值,往大的那边走就行,这里我遇到了问题,不知道是精度问题还是什么问题,有两组数据死活过不去,我加了一个判断,当前值大于最近的左端点和右端点的值,直接退出查找,去掉这句就AC,删了就wa= =
代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int mm=111111;
double f[mm],a[mm],b[mm],r[mm],X[mm],Y[mm],tmp;
struct SplayTree
{
int son[mm][2],far[mm],p[mm],tp[mm][2];
int rt,size;
void Link(int x,int y,int c)
{
far[x]=y,son[y][c]=x;
}
void Rotate(int x,int c)
{
int y=far[x];
Link(x,far[y],son[far[y]][1]==y);
Link(son[x][!c],y,c);
Link(y,x,!c);
}
void Splay(int x,int g)
{
for(;far[x]!=g;)
{
int y=far[x],cx=son[y][1]==x,cy=son[far[y]][1]==y;
if(far[y]==g)Rotate(x,cx);
else
{
if(cx==cy)Rotate(y,cy);
else Rotate(x,cx);
Rotate(x,cy);
}
}
if(!g)rt=x;
}
void NewNode(int y,int &x,int i)
{
x=++size;
far[x]=y,p[x]=i;
tp[x][0]=tp[x][1]=son[x][0]=son[x][1]=0;
}
void Insert(int i)
{
int x=rt,y,f;
while(son[x][f=(X[p[x]]<X[i])])x=son[x][f];
NewNode(x,son[x][f],i);
y=tp[size][f]=tp[x][f];
tp[y][!f]=tp[x][f]=size;
tp[size][!f]=x;
Splay(size,0);
Maintain();
}
void Prepare()
{
NewNode(size=0,rt,1);
}
bool TurnRight(int a,int b,int c)
{
if(!a||!c)return 1;
return (X[a]-X[b])*(Y[c]-Y[b])>(Y[a]-Y[b])*(X[c]-X[b]);
}
void Right()
{
int x=son[rt][1],y=rt;
while(x)
{
if(TurnRight(p[rt],p[x],p[tp[x][1]]))y=x,x=son[x][0];
else x=son[x][1];
}
if(y!=rt)
{
Splay(y,rt);
son[y][0]=0;
tp[rt][1]=y;
tp[y][0]=rt;
}
}
void Left()
{
int x=son[rt][0],y=rt;
while(x)
{
if(TurnRight(p[tp[x][0]],p[x],p[rt]))y=x,x=son[x][1];
else x=son[x][0];
}
if(y!=rt)
{
Splay(y,rt);
son[y][1]=0;
tp[rt][0]=y;
tp[y][1]=rt;
}
}
void Maintain()
{
if(son[rt][1])Right();
if(son[rt][0])
{
Left();
Splay(son[rt][0],0);
Right();
}
}
double Get(int i,int j)
{
return X[p[j]]*a[i]+Y[p[j]]*b[i];
}
double Find(int i)
{
int x=rt;
double ret=Get(i,x),tmp1,tmp2;
while(x)
{
x=son[x][Get(i,tp[x][0])<Get(i,tp[x][1])];
ret=max(ret,Get(i,x));
}
if(x)Splay(x,0);
return ret;
}
}spt;
double get()
{
char c;
while((c=getchar())<'0'||c>'9');
double a=c-'0',b=1;
while((c=getchar())>='0'&&c<='9')a=a*10+c-'0';
if(c=='.')
while((c=getchar())>='0'&&c<='9')b*=10.0,a=a+(c-'0')/b;
return a;
}
int i,n;
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
while(~scanf("%d%lf",&n,&f[1]))
{
for(i=1;i<=n;++i)
a[i]=get(),b[i]=get(),r[i]=get();
Y[1]=f[1]/(r[1]*a[1]+b[1]);
X[1]=Y[1]*r[1];
spt.Prepare();
for(i=2;i<=n;++i)
{
f[i]=spt.Find(i);
f[i]=max(f[i],f[i-1]);
Y[i]=f[i]/(r[i]*a[i]+b[i]);
X[i]=Y[i]*r[i];
spt.Insert(i);
}
printf("%.3lf\n",f[n]);
}
return 0;
}