[NOI2007]货币兑换

好像是二刷了吧,但是肯定不止见过两次了。

壹、题目描述

传送门 to LUOGU

贰、蒟蒻の思考

不难发现 题目提示也给了 必定存在一种最优方案满足买入时用完所有钱,卖出时卖完所有股票。

先分析清楚题,假如说在第 \(i\) 天用 \(S\) 元买股票,那么最后

\[\text{count}_B={S\over A_i\text{Rate}_i+B_i} \\ \text{count}_A=\text{count}_B\text{Rate}_i={S\times \text{Rate}_i\over A_i\text{Rate}_i+B_i} \]

那么我们定义 \(f_i\) 表示第 \(i\) 天能够得到的最多的钱,那么就有一个 \(\mathcal O(n^2)\) 的转移:

\[f_i=\max_{j=1}^{i-1}\{A_i\times {f_j\times \text{Rate}_j\over A_j\text{Rate}_j+B_j}+B_i\times {f_j\over A_j\text{Rate}_j+B_j}\} \]

最后还要 \(f_i=\max\{f_i,f_{i-1}\}\) 表示今天什么都没干,初始状态 \(f_0=s\).

十分容易地拿下 \(60pts\),考虑优化这个 \(\mathcal O(n^2)\)\(\tt DP\).

定义 \(t_i=A_i\text{Rate}_i+B_i\),那么转移写作

\[f_i=\max_{j=0}^{i-1}\{A_i\times {f_j\text{Rate}_j\over t_j}+B_i\times {f_j\over t_j}\} \]

发现这两个分数都只和 \(j\) 有关,那么这个式子就是可以斜率优化的。

\(x_j={f_j\text{Rate}_j\over t_j},y_j={f_j\over t_j}\),我们将这个方程改写一下:

\[f_i=A_ix_j+B_iy_j \\ \Rightarrow y_j=-{A_i\over B_i}x_j+{f_i\over B_i} \]

从含义上讲,即对于之前的所有决策点,在平面上记其为 \(\lang x_j,y_j\rang\),然后用 \(-{A_i\over B_i}\) 的直线去任意切一个,得到的最大截距就是 \(f_i\over B_i\),然后就可以算出最大 \(f_i\) 了。

但是有一个问题,那就是这个 \(x_j\) 并不是单调的,所以我们还不能使用单调队列对其进行维护,但是我们可以使用 \(\tt CDQ\) 分治,每次把左边的 \(x_i\) 进行排序,考虑左边对右边的贡献,维护一个不动凸包,然后在凸包上面二分即可,这样做复杂度 \(\mathcal O(n\log^2 n)\). 其实也不需要再凸包上进行二分,只需要将右边按照斜率 \(k=-{A_i\over B_i}\) 排序,每次在凸包前面查看是否有更优解,有就将队头弹出,直到找到最优解更新答案即可。

叁、参考代码

#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
template<class T>inline T fab(const T x){return x<0? -x: x;}
template<class T>inline T readin(T x){
    x=0; int f=0; char c;
    while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
    for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
    return f? -x: x;
}

const int maxn=1e5;
const double eps=1e-8;
typedef struct Double{
	double x;
	Double(){}
	Double(const double X): x(X){}
	inline int operator <(const Double rhs) const{
		return x<rhs.x;
	}
	inline int operator >(const Double rhs) const{
		return rhs<(*this);
	}
	inline int operator ==(const Double rhs) const{
		return fab(x-rhs.x)<eps;
	}
	inline int operator <=(const Double rhs) const{
		return (*this)<rhs || (*this)==rhs;
	}
	inline int operator >=(const Double rhs) const{
		return (*this)>rhs || (*this)==rhs;
	}
	inline Double operator -(const Double rhs) const{
		return x-rhs.x;
	}
	inline Double operator +(const Double rhs) const{
		return x+rhs.x;
	}
	inline Double operator *(const Double rhs) const{
		return x*rhs.x;
	}
	inline Double operator /(const Double rhs) const{
		return x/rhs.x;
	}
	inline Double operator -() const{
		return -x;
	}
} doub;

int n;

doub a[maxn+5], b[maxn+5], rate[maxn+5];
doub x[maxn+5], y[maxn+5], f[maxn+5];

inline void input(){
	n=readin(1); scanf("%lf", &f[1].x);
	for(int i=1; i<=n; ++i){
		scanf("%lf %lf %lf", &a[i].x, &b[i].x, &rate[i].x);
	}
}

inline int comp(const doub a,const doub b){
	return a.x>b.x;
}
inline int cmpx(const int i, const int j){
	return x[i]<x[j];
}
inline int cmpslope(const int i, const int j){
	return -a[i]/b[i]>-a[j]/b[j];
}
inline doub slope(const int i, const int j){
	if(x[i]==x[j]) return (y[i]-y[j])*1e8;
	return (y[i]-y[j])/(x[i]-x[j]);
}
inline doub slope(const int i){
	return -a[i]/b[i];
}
int q[maxn+5], op, ed;
int id[maxn+5];
void solve(const int l, const int r){
	if(l==r){
		y[l]=x[l]/rate[l];
		return;
	}
	int mid=(l+r)>>1;
	solve(l, mid);
	for(int i=l; i<=r; ++i) id[i]=i;
	sort(id+l, id+mid+1, cmpx);
	sort(id+mid+1, id+r+1, cmpslope);
	op=1, ed=0;
	for(int i=l; i<=mid; ++i){
		while(op<ed && slope(q[ed-1], q[ed])<slope(q[ed], id[i])) --ed;
		q[++ed]=id[i];
	}
	for(int i=mid+1; i<=r; ++i){
		while(op<ed && slope(q[op], q[op+1])>=slope(id[i])) ++op;
		f[id[i]]=max(f[id[i]], a[id[i]]*x[q[op]]+b[id[i]]*y[q[op]]);
	}
	for(int i=mid+1; i<=r; ++i){
		f[i]=max(f[i], f[i-1]);
		x[i]=rate[i]*f[i]/(a[i]*rate[i]+b[i]);
	}
	solve(mid+1, r);
}

signed main(){
	input();
	x[1]=f[1]*rate[1]/(a[1]*rate[1]+b[1]);
	solve(1, n);
	printf("%.4f\n", f[n].x);
    return 0;
}

有些地方精度太高了,调了好久......卡进度真的没意思。

肆、用到の小 \(\tt trick\)

有时候斜率并不能保证一些东西是单调的,这个时候你就需要使用动态凸包或者用 \(\tt CDQ\) 处理这种坐标不保证单调的情况。

另外,对于 \(\tt DP\) 方程的变形,要将和 \(i\) 有关的,和 \(j\) 有关的变量进行剥离,这样才能更好地处理。

posted @ 2021-02-23 16:06  Arextre  阅读(52)  评论(0编辑  收藏  举报