洛谷P4027,[NOI2007]货币兑换,CDQ分治+斜率优化+维护凸包

正题

      题目直接戳这里

      首先,要贪心一点,我们肯定会选在j天买入i天卖出收益最大的时候整体买入和整体卖出。

      所以很明显就有一个Dp方程。f(i)=max(f(i-1),\max_{j=1}^{x-1}a_i*x_j+b_i*y_j)

      其中ai指的是在这时候a卷的价值,bi同理。

      xj表示在j的时候买的最多的xj的卷数。

      yj同理

      然后遇到这种东西不知道怎么办就只能暴力找。

      n的平方。

      想着怎么优化。

      化简一下。

      \\f(i)=a_i*x_j+b_i*y_j \\\therefore y_j=\frac{f(i)-a_i*x_j}{b_i} \\\therefore y_j=\frac{-a_i}{b_i}x_j+\frac{f(i)}{b_i}

      诶.y=kx+b耶。想使得f(i)最大,那么必须使得b(截距)最大。那么很明显,用前i-1个点在笛卡尔坐标系上描点,用斜率为\frac{-a_i}{b_i}的直线从上往下扫,碰到的第一个点就是能使i获得最大价值的j。明显是个上凸包。但是题目没有保证-\frac{a_i}{b_i}有序,所以不能用单调队列来维护。想想,是不是还有CDQ分治啊。

      所以我们把每一个i按照对应的斜率进行排序。

      每次分的时候,我们把i分成左右两堆,一堆的初始序是小于等于mid,一堆的初始序是大于mid的,那么现在可以保证,左右两堆虽然不是按顺序的,但是去到了自己要去的地方,而且是按斜率排序的。

       每次处理完左边,就要计算一下左边对右边产生的影响。

       用左边构造一个上凸包,又因为右边的斜率是有序的,所以我们可以花(r-l+1)/2的时间扫完整个凸包,并且获得右边一些点的答案(因为有些点的答案不存在于这些点内)。

       处理完左右两边,我们按照x排序,返回,这样又可以使得上一层可以O(n)构造凸包。

       另外x_iy_i是可以通过解一个一元二次方程得到的(当然要知道f(i)才能求)。

       另外,做这种毒瘤题一定要注意精度

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;

int n;
double eps=1e-8;
double INF=1e20;
struct node{
	double a,b,r,k,x,y;
	int id;
	bool operator<(const node p)const{
		return k>p.k;
	}
}q[100010],t[100010];
double f[100010];
int op[100010];

double get_k(int a,int b){
	if(b==0) return -INF;
	if(fabs(q[a].x-q[b].x)<eps) return INF;
	return (q[b].y-q[a].y)/(q[b].x-q[a].x);
}

void solve(int l,int r){
	if(l==r){
		f[l]=max(f[l-1],f[l]);
		q[l].y=f[l]/(q[l].a*q[l].r+q[l].b);
		q[l].x=q[l].y*q[l].r;
		return ;
	}
	int mid=(l+r)/2;
	int left=l,right=mid+1;
	for(int i=l;i<=r;i++)
		if(q[i].id<=mid) t[left++]=q[i];
		else t[right++]=q[i];
	for(int i=l;i<=r;i++) q[i]=t[i];
	solve(l,mid);
	int tt=0;
	for(int i=l;i<=mid;i++){
		while(tt>1 && get_k(op[tt-1],op[tt])<get_k(op[tt-1],i)+eps) tt--;
		op[++tt]=i;
	}
	op[++tt]=0;
	int now=1;
	for(int i=mid+1;i<=r;i++){
		while(now<tt && get_k(op[now],op[now+1])+eps>q[i].k) now++;
		f[q[i].id]=max(f[q[i].id],q[op[now]].x*q[i].a+q[op[now]].y*q[i].b);
	}
	solve(mid+1,r);
	left=l,right=mid+1;
	for(int i=l;i<=r;i++)
		if(((q[left].x<q[right].x||(fabs(q[left].x-q[right].x)<eps && q[left].y<q[right].y))||right>r)&&left<=mid)
			t[i]=q[left++];
		else t[i]=q[right++];
	for(int i=l;i<=r;i++) q[i]=t[i];
	return ;
}

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;
	}
	sort(q+1,q+1+n);
	solve(1,n);
	printf("%.3lf",f[n]);
}

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值