来自FallDream的博客,未经允许,请勿转载,谢谢。
题目好难解释啊,大家自己看原题吧??
小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 的变化分别为:
假定在第一天时,用户手中有 100元 人民币但是没有任何金券。用户可以执行以下的操作:
注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能够获得多少元钱。
【提示】
必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币;每次卖出操作卖出所有的金券。
n<=100000,0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤10^9。
题解:首先这个提示已经告诉了你这是一个dp啦。用f[i]表示前i天最多能获得的钱,那么假设这一天全部买进,那么获得的金券也就确定了。
n^2dp很simple,假设x[i],y[i]分别表示第i天最多买进的ab两种金券的数量,那么f[i]=max(f[i-1],x[j]*a[i]+y[j]*b[i])
考虑斜率优化,f[i]=max(x[j]*a[i]+y[j]*b[i]),y[j]=(-a[i]/b[i])*x[j]+f[i]/b[i],-a[i]/b[i]看作斜率,表示成了关于平面x,y上的斜率关系。bi不变,要让fi最大,就要让截距最大。
很容易发现,满足的点都在(x,y)的上凸壳上,所以我们维护这个凸壳。
怎么维护凸壳呢?下面介绍两种方法。
1)splay
由于xi不一定是连续的,所以不能用单调队列,所以用splay把所有点维护起来,每个点记录和左右两边的点的斜率。
每次查询,我们用一条(-ai/bi)的直线去切割这个凸壳,就可以找到最优的转移点。
每次插入,我们让按x坐标插入,然后往两边找到它能作为凸壳时连接的点,删去中间的点。假如找不到,也就说明它在凸壳内,把它自己删掉就行了。
这样我们就能在O(nlogn)的时间内解决该问题。
2)cdq分治
但是我们还有更简单的办法,那就是cdq分治。我发现我对于每个i做的事情,都是用它的斜率在前面找一个点来更新他,而复杂点在于x的无序,不能用单调队列维护。如果我能让x和查询的斜率变成有序的,我就可以用单调队列快速完成。很自然想到cdq分治,我保证每一段的斜率有序,在分治结束后,把它的坐标再归并起来,这样在对一段区间"治"的时候,左边的点有序,可以直接算出凸壳,右边的询问也有序,可以直接查询。
这样更新一段区间的复杂度是O(n)的,最多log层,加上初始的排序也是nlogn,总复杂度nlogn。
跑起来会比splay慢一些些,但是代码复杂度方面,稳胜splay
附上两种方法的代码
splay维护凸壳:
#include<iostream> #include<cstdio> #include<cmath> #define eps 1e-8 #define MN 100000 #define INF 2000000000 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int fa[MN+5],c[MN+5][2],n,rt=0; double a[MN+5],b[MN+5],x[MN+5],y[MN+5],r[MN+5],L[MN+5],R[MN+5],f[MN+5]; inline double getslop(int j,int k) { if(fabs(x[j]-x[k])<eps)return -INF; return (y[j]-y[k])/(x[j]-x[k]); } void rotate(int x,int&k) { int y=fa[x],z=fa[y],l=c[y][1]==x,r=l^1; if(y!=k) c[z][c[z][1]==y]=x;else k=x; fa[x]=z;fa[y]=x;fa[c[x][r]]=y; c[y][l]=c[x][r];c[x][r]=y; } void splay(int x,int&k) { while(x!=k) { int y=fa[x],z=fa[y]; if(y!=k) { if(c[y][1]==x^c[z][1]==y)rotate(x,k); else rotate(y,k); } rotate(x,k); } } int get(int x,double now) { if(!x)return 0; if(L[x]+eps>=now&&now+eps>=R[x]) return x; return get(c[x][L[x]+eps>now],now); } void ins(int&now,int last,int k) { if(!now){now=k;fa[k]=last;return;} ins(c[now][x[k]>x[now]+eps],now,k); } int ask_before(int x,int k) { if(!x)return 0;int q; if(getslop(k,x)+eps>=R[x])return (q=ask_before(c[x][0],k))?q:x; else return ask_before(c[x][1],k); } int ask_after(int x,int k) { if(!x)return 0;int q; if(getslop(x,k)<=L[x]+eps)return (q=ask_after(c[x][1],k))?q:x; else return ask_after(c[x][0],k); } void repair(int k) { splay(k,rt); if(c[k][0]) { int l=ask_before(c[k][0],k); if(l) { splay(l,c[k][0]);c[l][1]=0; R[l]=L[k]=getslop(k,l); } else L[k]=-INF; } else L[k]=INF; if(c[k][1]) { int r=ask_after(c[k][1],k); if(r) { splay(r,c[k][1]);c[r][0]=0; L[r]=R[k]=getslop(r,k); } else R[k]=INF; } else R[k]=-INF; if(L[k]<=R[k]+eps) { int l=c[k][0],r=c[k][1];rt=l?l:r; fa[r]=l;c[l][1]=r;fa[l]=0; L[r]=R[l]=getslop(r,l); } } int main() { n=read();scanf("%lf",&f[0]); for(int i=1;i<=n;i++)scanf("%lf%lf%lf",&a[i],&b[i],&r[i]); for(int i=1;i<=n;i++) { int j=get(rt,-(a[i]/b[i])); f[i]=max(f[i-1],a[i]*x[j]+b[i]*y[j]); y[i]=f[i]/(a[i]*r[i]+b[i]); x[i]=y[i]*r[i]; ins(rt,0,i);repair(i); } printf("%.3lf",f[n]); return 0; }
cdq分治 好写多了。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #define eps 1e-8 #define MN 100000 #define INF 2000000000 using namespace std; inline int read() { int x = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } struct P{ double a,b,x,y,r,slop;int id; bool operator<(const P&b)const{return slop>b.slop;} }p[MN+5],t[MN+5]; double f[MN+5]; int n,q[MN+5]; double getslop(int x,int y) { if(!y)return -INF; if(fabs(p[x].x-p[y].x)<eps)return INF; return (p[y].y-p[x].y)/(p[y].x-p[x].x); } void solve(int l,int r) { if(l==r) { f[l]=max(f[l-1],f[l]); p[l].y=f[l]/(p[l].a*p[l].r+p[l].b); p[l].x=p[l].y*p[l].r; return; } int mid=l+r>>1,cnt1=l,cnt2=mid+1,top=0,j=1; for(int i=l;i<=r;i++) t[p[i].id<=mid?cnt1++:cnt2++]=p[i]; for(int i=l;i<=r;i++)p[i]=t[i]; solve(l,mid); for(int i=l;i<=mid;i++) { while(top>1&&getslop(q[top-1],q[top])<getslop(q[top],i)+eps) --top; q[++top]=i; } for(int i=mid+1;i<=r;i++) { while(j<top&&getslop(q[j],q[j+1])+eps>p[i].slop) ++j; f[p[i].id]=max(f[p[i].id],p[q[j]].x*p[i].a+p[q[j]].y*p[i].b); } solve(mid+1,r);cnt1=l,cnt2=mid+1; for(int i=l;i<=r;i++) if(cnt2>r||(cnt1<=mid&&(p[cnt1].x<p[cnt2].x+eps ||(fabs(p[cnt1].x-p[cnt2].x)<eps&&p[cnt1].y<p[cnt2].y+eps)))) t[i]=p[cnt1++]; else t[i]=p[cnt2++]; for(int i=l;i<=r;i++)p[i]=t[i]; } int main() { n=read();scanf("%lf",&f[0]); for(int i=1;i<=n;i++) { scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].r); p[i].slop=(-p[i].a/p[i].b);p[i].id=i; } sort(p+1,p+n+1); solve(1,n); printf("%.3lf ",f[n]); return 0; }