ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P4027 [NOI2007]货币兑换 题解

2022-04-21 15:04:57  阅读:170  来源: 互联网

标签:P4027 frac int 题解 mid NOI2007 tail cdq inline


Post time: 2020-12-02 21:08:23

题意简述:传送门

一共 \(n\) 天,每天可以卖出或者买入两种股票 \(A\) 和 \(B\)。这两种股票在第 \(i\) 天的价值为 \(A_i\) 和 \(B_i\)。

每天可以花所有的现金买入股票,这些股票中 \(A\) 股与 \(B\) 股的数量比为 \(Rate_i\)。每天也可以把所有的股票以当天的价值卖出,获得现金。已知接下来 \(n\) 天的 \(A_i,B_i,Rate_i\),求出 \(n\) 天后能够获得的最大价值。

请注意本题冗余描述过多,其实只有数据范围下面那句话是有用的,做题的时候一定要注意提取关键信息!


本人的解法:cdq 分治维护单调栈斜率优化 dp

考虑到每一次买花掉全部的钱,每一次卖全部卖掉,我们不妨设 \(f_i\) 表示第 \(i\) 天能够拥有的最大钱数,把买股票算在之后每一天当中,即:

设 \(x_k\) 表示第 \(k\) 天买的 \(A\) 股数量,\(y_k\) 表示第 \(k\) 天买的 \(B\) 股数量,\(a_k,b_k,r_k\) 分别表示第 \(k\) 天的 \(A\) 股价值、\(B\) 股价值和 \(AB\) 两股数量比,可得:

\[\left\{ \begin{aligned} & x_k\cdot a_k+y_k\cdot b_k=f_k \\ & \frac{x_k}{y_k}=r_k \\ \end{aligned} \right. \]

解得

\[\left\{ \begin{aligned} & x_k=\frac{f_k r_k}{a_k r_k+b_k} \\ & y_k=\frac{f_k}{a_k r_k+b_k} \\ \end{aligned} \right. \]

接下来我们考虑第 \(i\) 天,卖股票能够得到的最大价值,即我们从 \(1\) 到 \(i-1\) 枚举一个 \(j\),那么第 \(i\) 天就能卖掉第 \(j\) 天买的 \(A\) 股(数量 \(x_j\)),\(B\) 股(数量 \(y_j\))。所以我们可以得到这样一个方程:

\[f_i=a_ix_j+b_iy_j \]

暴力的时间复杂度是 \(O(n^2)\),期望得分 \(60\)。

接下来,由于出现了 \(i,j\) 相乘项,考虑斜率优化。首先把它化成斜截式方程,即:

\[y_j=-\frac{a_i}{b_i}x_j+\frac{f_i}{b_i} \]

它的意义是在平面内有很多点 \((x_j,y_j)\),对每个 \(i\) 找到一条斜率为 \(-\frac{a_i}{b_i}\) 的直线穿过其中某个点能够得到的最大截距。

做法很明显,维护一个上凸壳,对每个 \(k_i\) 找最优决策点即可。然而——这道题的 \(x_i\) 和 \(k_i\) 无序,所以一无法使用单调队列(需要 \(x_i\) 和 \(k_i\) 均有序),二无法使用单调栈内二分(\(k_i\) 需有序)。这时候我们需要使用 cdq 分治来维护。

具体维护方法如下(把每一天需要的所有状态放进一个 struct 数组中):

  1. 在进入 cdq(l,r) 之前,先把整个数组按照 \(k\) 排序。
  2. 当 \(l=r\) 时,更新第 \(l\) 个状态的 \(x_l,y_l\)。
  3. [l,r] 中天数 \(\leq mid\) 的放在左半边,剩下的放在右半边。
  4. 递归处理出 [l,mid] 天的 \(f_i,x_i,y_i\)。
  5. 用单调栈统计 [l,mid] 天对 [mid+1,r] 天的贡献。
  6. 递归处理 [mid+1,r] 天的 \(f_i,x_i,y_i\)。
  7. 将整个区间 [l,r] 按照 \(x\) 递增排序。

我们可以通过这些步骤,观察出 cdq 分治维护的优秀性质:在第 4 步,能用单调栈线性统计的原因是,对于 [l,mid],因为我们在第 7 步把 [l,mid] 按照 \(x\) 单增排序了,同时,在第 0 步对整个区间按照 \(k\) 单增排序了,第一步的操作会使 \(k\) 在 [mid+1,r] 上单增。所以可以使用单调栈统计。

其实根本原因还是在于 cdq 分治的优越性:只需要考虑 [l,mid][mid+1,r] 的贡献,所以可以分别排序,把原本无法维护的东西花一个 \(\log\) 的代价变成可以维护。

此题总的时间复杂度 \(O(n \log n)\)。

最后放上 AC 代码:

点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+13;
const double eps=1e-9;
const double INF=1e9; 
struct Node{double a,b,r,k,x,y;int id;}Q[N],Tmp[N];
struct Queue{//本人比较习惯手写队列(这题好像是个栈,不过不重要)
	int q[N],head,tail;
	inline void clear(){q[head=tail=1]=0;}
	Queue(){clear();}
	inline void push(int x){q[++tail]=x;}
	inline void pop(){--tail;}
	inline int top(){return q[tail];}
	inline int ttop(){return q[tail-1];}
	inline bool empty(){return head>=tail;}
}s;
double f[N];int n;
inline bool cmp0(const Node &a,const Node &b){return a.k<b.k;}
inline bool cmp1(const Node &a,const Node &b){return a.x<b.x;}
inline double slope(int i,int j){
	if(fabs(Q[i].x-Q[j].x)<eps) return INF;//这里没判会WA on #6,#7!!
	return (Q[i].y-Q[j].y)/(Q[i].x-Q[j].x);
}
void cdq(int l,int r){
	if(l==r){//第1步
		f[l]=max(f[l],f[l-1]);//别忘了还可以在这一天当中什么都不干
		Q[l].y=f[l]/(Q[l].a*Q[l].r+Q[l].b),Q[l].x=Q[l].r*Q[l].y;//更新x,y的值,接下来可以用它们去更新其他天的f值。
		return;
	}
	int mid=(l+r)>>1;
	int l1=l-1,l2=mid;
	for(int i=l;i<=r;++i){//第2步,将[l,r]化为左右两个k单调的区间,将询问放在相应的区间内处理
		if(Q[i].id<=mid) Tmp[++l1]=Q[i];
		else Tmp[++l2]=Q[i];
	}
	for(int i=l;i<=r;++i) Q[i]=Tmp[i];
	cdq(l,mid);//第3步
	s.clear();
	for(int i=l;i<=mid;++i){//预处理出上凸包
		while(!s.empty()&&slope(s.top(),i+eps)>slope(s.ttop(),s.top())) s.pop();
		s.push(i);
	}
	for(int i=mid+1,j;i<=r;++i){//第4步
		while(!s.empty()&&Q[i].k+eps>slope(s.ttop(),s.top())) s.pop();//因为斜率递增所以可以直接pop
		f[Q[i].id]=max(f[Q[i].id],Q[j=s.top()].x*Q[i].a+Q[j].y*Q[i].b);
	}
	cdq(mid+1,r);//第5步
	sort(Q+l,Q+r+1,cmp1);//第6步,偷懒了没写归并,这样应该也没问题
}
int main(){
	scanf("%d%lf",&n,&f[0]);
	for(int i=1;i<=n;++i){
		scanf("%lf%lf%lf",&Q[i].a,&Q[i].b,&Q[i].r);
		Q[i].k=-Q[i].a/Q[i].b;Q[i].id=i;//预处理k和id
	}
	sort(Q+1,Q+n+1,cmp0);//第0步
	cdq(1,n);
	printf("%.3lf\n",f[n]);
	return 0;
}

标签:P4027,frac,int,题解,mid,NOI2007,tail,cdq,inline
来源: https://www.cnblogs.com/winterfrost/p/16174123.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有