把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ1492】[NOI2007] 货币兑换(斜率优化+CDQ分治)

点此看题面

大致题意:\(n\)天,起初有\(S\)元钱。每天有三个参数\(A_i,B_i,R_i\),表示当天\(A\)券的价格、当天\(B\)券的价格以及当天如果要买进金券\(A\)券和\(B\)券所需的比值。每天可以卖完所持金券,也可以用所有钱买入金券,求\(n\)天后能获得的最大钱数。

斜率优化

考虑\(DP\),设\(f_i\)为第\(i\)天结束时最大钱数。

方便起见,令\(x(i)\)为当天最大买入\(A\)券数\(R_i\times \frac{f_i}{R_iA_i+B_i}\)\(y_i\)为当天最大买入\(B\)券数\(\frac{f_i}{R_iA_i+B_i}\)

那么就有一个暴力转移:

\[f_i=min\{A_i\times x_j+B_i\times y_j\} \]

按照斜率优化的常见套路,我们求出当\(j\)的答案优于\(k\)的答案时需要满足的条件:

\[A_ix_j+B_iy_j>A_ix_k+B_iy_k \]

\[A_i(x_j-x_k)>B_i(y_k-y_j) \]

\[-\frac{A_i}{B_i}\times (x_j-x_k)<y_j-y_k \]

此时容易想到要把\(x_j-x_k\)移到右边去,为了保证符号不改变,强制\(x_j>x_k\),得到:

\[\frac{y_j-y_k}{x_j-x_k}>-\frac{A_i}{B_i} \]

这样看起来似乎可以直接套路地单调队列优化\(DP\)了?

然而,并不行!

为什么呢?因为这题有两个东西都不满足单调性:

  • \(-\frac{A_i}{B_i}\):这个东西不满足单调性,因此你当前不优的答案不意味着以后不优,不能舍去。
  • \(x_i\):这个才是关键,毕竟如果只是上面那家伙还可以用二分,可\(x_i\)不满足单调性,我们的转移条件又需要\(x_i\)是有序的,就非常麻烦了。

这样一来,似乎是要满足在区间中插入一个数,还要可以实现二分操作。

想到了什么?\(Splay\)

但是写个数据结构毕竟太麻烦了,因此这里我们讨论一种较为简单的离线做法:\(CDQ\)分治。

\(CDQ\)分治

考虑我们以编号为分治区间,这样每次都满足左区间一定能向右区间转移。

然后看看我们要人为使其满足单调性的两个东西,恰好一个针对当前点(\(-\frac{A_i}{B_i}\)),一个针对转移点(\(x_i\))。

于是,我们把左区间按\(x_i\)排序,右区间按\(-\frac{A_i}{B_i}\)排序。

对于左区间,扫一遍,维护一个单调栈满足其斜率递减。

对于右区间,每次弹出单调栈顶部斜率小于等于\(-\frac{A_i}{B_i}\)的点,这样就可以保证栈顶一定是当前最优的转移点。

于是这道题就套路地做完了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB double
#define eps 1e-8
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
int n,m,S[N+5];DB a[N+5],b[N+5],R[N+5],f[N+5];
struct Point
{
	DB x,y;I Point(Con DB& a=0,Con DB& b=0):x(a),y(b){}
	I bool operator < (Con Point& o) Con {return fabs(x-o.x)>eps?x<o.x:y>o.y;}//按x排序
}p[N+5];
struct Data
{
	int p;DB k;I Data(CI i=0,Con DB& t=0):p(i),k(t){}
	I bool operator < (Con Data& o) Con {return k<o.k;}//按斜率排序
}s[N+5];
I void CDQ(CI l=1,CI r=n)//CDQ分治
{
	if(l==r) return (void)(f[l]=max(f[l],m));RI i,mid=l+r>>1;CDQ(l,mid);//处理左区间
	RI cnt=0;for(i=l;i<=mid;++i) p[++cnt]=Point(R[i]*f[i]/(R[i]*a[i]+b[i]),f[i]/(R[i]*a[i]+b[i]));//存点
	RI tot=0;for(i=mid+1;i<=r;++i) s[++tot]=Data(i,-a[i]/b[i]);//存信息
	#define K(j,k) (p[j].y-p[k].y)/(p[j].x-p[k].x)
	RI T=0;for(sort(p+1,p+cnt+1),i=1;i<=cnt;++i)//求单调栈
	{
		if(i^1&&p[i].x-p[i-1].x<eps) continue;//特判x相同的情况
		W(T>1&&K(i,S[T])>K(S[T],S[T-1])) --T;S[++T]=i;//维护单调栈单调性
	}
	for(sort(s+1,s+tot+1),i=1;i<=tot;++i)//动态规划
	{
		W(T>1&&K(S[T],S[T-1])<=s[i].k) --T;//弹走不优的解
		f[s[i].p]=max(f[s[i].p],a[s[i].p]*p[S[T]].x+b[s[i].p]*p[S[T]].y);//DP转移
	}
	for(i=mid+1;i<=r;++i) f[i]=max(f[i],f[i-1]);CDQ(mid+1,r);//处理右区间
}
int main()
{
	RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%lf%lf%lf",a+i,b+i,R+i);
	return CDQ(),printf("%.3lf",f[n]),0;
}
posted @ 2020-06-05 08:33  TheLostWeak  阅读(132)  评论(0编辑  收藏  举报