【BZOJ 1492】[NOI2007]货币兑换Cash cdq分治+斜率dp

236 篇文章 0 订阅
11 篇文章 0 订阅

每一天的-a[i]/b[i]作为绿色的斜率k值,等价于求最右上的那条绿线与红线交点。维护一个凸包,将绿线一开始以k大小排序,这样一来就都具有单调性了,就可以使用斜率优化dp了

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#define maxn 100020
#define eps 1e-9
#define LL long long 
#include<algorithm>
using namespace std;
int n,s,sta[maxn];
double f[maxn];
struct node{
	int id;
	double a,b,x,y,k,r;
	bool operator <(const node& bb)const{return k>bb.k;}
}p[maxn],q[maxn];

double getk(int a,int b){
	if(!b)return -1e20;
	if(fabs(p[a].x-p[b].x)<eps)return 1e20;
	return (p[b].y-p[a].y)/(p[b].x-p[a].x);
}
void solve(int l,int r){
	if(l==r){
		f[l]=max(f[l],f[l-1]);
		p[l].y=f[l]/(p[l].a*p[l].r+p[l].b);
		p[l].x=p[l].y*p[l].r;
		return;
	}
	int mid=l+r>>1,j=l,k=mid+1;
	for(int i=l;i<=r;i++)
	    if(p[i].id<=mid)q[j++]=p[i];else q[k++]=p[i];
	for(int i=l;i<=r;i++)p[i]=q[i];
	solve(l,mid);
	
	//跑凸包 
	k=0;
	for(int i=l;i<=mid;i++){
		while(k>1&&getk(sta[k],i)+eps>getk(sta[k-1],sta[k]))k--;
		sta[++k]=i;
	}sta[++k]=0,j=1;
	for(int i=mid+1;i<=r;i++){
		while(j<k&&getk(sta[j],sta[j+1])+eps>p[i].k)j++;
		f[p[i].id]=max(f[p[i].id],p[sta[j]].x*p[i].a+p[sta[j]].y*p[i].b);
	}
	solve(mid+1,r);j=l,k=mid+1;
	for(int i=l;i<=r;i++){
		if(j<=mid&&((fabs(p[j].x-p[k].x)<eps&&p[j].y<p[k].y)||p[j].x<p[k].x||k>r))
		    q[i]=p[j++];
		else q[i]=p[k++];
	}
	for(int i=l;i<=r;i++)p[i]=q[i];
}

int main(){
	scanf("%d%lf",&n,f);
	for(int i=1;i<=n;i++){
		scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].r);
		p[i].k=-p[i].a/p[i].b;p[i].id=i;
	}
	sort(p+1,p+1+n);
	solve(1,n);
	printf("%.3lf",f[n]);
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值