http://www.lydsy.com/JudgeOnline/problem.php?id=1492
dp[i] 表示 第i天卖完的最大收益
朴素的dp:
枚举从哪一天买来的在第i天卖掉,或者是不操作
dp[i]=max(dp[i-1],X[j]*A[i]+Y[j]*B[i])
其中X[j]表示在第j天能买多少A纪念券,Y[j]表示在第j天能买多少B纪念券
可列方程 X[j]*A[j]+Y[j]*B[j]=dp[j]
又因为 X[j]=Rate[j]*Y[j]
所以解出 Y[j]=dp[j]/(B[j]+A[j]*Rate[j])
优化:
dp[i]=X[j]*A[i]+Y[j]*B[i]
Y[j]=dp[i]/B[i] - A[i]/B[i] * x[j]
斜率优化形式,维护上凸壳,最大化截距
点:(X[j],Y[j])
斜率:-A[i]/B[i]
但是 -A[i]/B[i] 不具有单调性
所以不能用单调队列维护斜率
可以用平衡树维护,O(n * logn * logn)
更简单的方法:CDQ分治
假设现在正在计算 dp[l]~dp[r],即solve(l,r)
对于每个j∈[l,r],[1,j-1]都是j的一种决策
令 mid=(l+r)/ 2
我们先计算出 dp[l]~dp[mid],然后用这些去更新 dp[mid+1]~dp[r]
假设我们现在已有了dp[l]~dp[mid]的上凸壳
那么如果保证 mid+1~r的斜率单调
就可以在线性时间内完成 dp[l]~dp[mid] 对 dp[mid+1]~dp[r]的更新
斜率单调可以在归并排序之前的排序预处理中解决
仅剩的问题:如何得到dp[l]~dp[mid] 构成的上凸壳?
在每一层最后归并的时候,我们用线形的时间使l~mid 的点 有序
即横坐标为第一关键字,纵坐标为第二关键字 升序排列,
那么处理完l~mid之后,
会得到l~mid 所有的点 升序排列的结果
对于一些有序的点,用单调栈维护斜率递减即可维护出上凸壳
然后利用这个上凸壳去更新mid+1~r 即可
复杂度为 O(nlogn)
#include<cmath> #include<cstdio> #include<algorithm> #include<iostream> using namespace std; #define N 100001 const double eps=1e-7; struct node { double A,B,R; double X,Y; double K; int id; bool operator < (node p) const { /*if(fabs(X-p.X)>eps) return X<p.X; return Y<p.Y;*/ return X<p.X; } }e[N],t[N]; double dp[N],ans; int st[N],top; bool down(int i,int j,int k) { return (e[j].X-e[i].X)*(e[k].Y-e[i].Y)-(e[k].X-e[i].X)*(e[j].Y-e[i].Y)<0; } void solve(int l,int r) { if(l==r) { dp[l]=max(dp[l],dp[l-1]); e[l].Y=dp[l]/(e[l].A*e[l].R+e[l].B); e[l].X=e[l].Y*e[l].R; return; } int mid=l+r>>1; int opl=l,opr=mid+1; for(int i=l;i<=r;++i) if(e[i].id<=mid) t[opl++]=e[i]; else t[opr++]=e[i]; for(int i=l;i<=r;++i) e[i]=t[i]; solve(l,mid); top=0; for(int i=l;i<=mid;++i) { while(top>1 && !down(st[top-1],st[top],i)) top--; st[++top]=i; } int now=1; for(int i=mid+1;i<=r;++i) { while(now<top && (e[st[now]].Y-e[st[now+1]].Y)<(e[st[now]].X-e[st[now+1]].X)*e[i].K) now++; dp[e[i].id]=max(dp[e[i].id],e[st[now]].X*e[i].A+e[st[now]].Y*e[i].B); } solve(mid+1,r); opl=l; opr=mid+1; for(int i=l;i<=r;++i) if(opl>mid) t[i]=e[opr++]; else if(opr>r) t[i]=e[opl++]; else if(e[opl]<e[opr]) t[i]=e[opl++]; else t[i]=e[opr++]; for(int i=l;i<=r;++i) e[i]=t[i]; } bool cmp(node p,node q) { return p.K>q.K; } int main() { int n; double m; scanf("%d%lf",&n,&dp[0]); for(int i=1;i<=n;++i) { scanf("%lf%lf%lf",&e[i].A,&e[i].B,&e[i].R); e[i].K=-e[i].A/e[i].B; e[i].id=i; } sort(e+1,e+n+1,cmp); solve(1,n); printf("%.3lf",dp[n]); }
1492: [NOI2007]货币兑换Cash
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 5830 Solved: 2342
[Submit][Status][Discuss]
Description
Input
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
Sample Input
1 1 1
1 2 2
2 2 3
Sample Output
HINT