CDQ分治+ DP BZOJ 1492 Cash

题目链接:[NOI2007]货币兑换Cash

分析请见CDQ论文: 从《Cash》谈一类分治算法的应用

代码如下:

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>

using namespace std;
const int maxn=200000+5;
const double inf= 999999999999.00, eps= 1e-9; 

int n,S;
int Q[maxn],rear;
double f[maxn];

struct node{
	int id;
	double x,y,a,b,k,rate;
	// x: 第一种商品的数量 y: 第二种商品的数量. 
	bool operator < (const node p) const {    // 按斜率 k= - a[i]/b[i] 降序排序 .
		return k > p.k;  	
	}
	void put(){
		cout<<x<<" "<<y<<endl; 
	} 
}s[maxn],temp[maxn];

double slope(int A,int B){
	if(B==0) return  -inf;
	if( fabs(s[A].x-s[B].x)< eps ) return inf;
	return (s[A].y-s[B].y) / (s[A].x-s[B].x);
}

void Solve(int L,int R){
	if(L==R){
		f[L]= max(f[L],f[L-1]);
		s[L].y= f[L]/(s[L].rate*s[L].a+s[L].b);
		s[L].x= s[L].y*s[L].rate;
		return ;
	}
	int mid= (L+R)>>1;
	int i=L,j=mid+1,p;
	// 按照原序排序,保证左边能更新右边,而且左右区间中 k= -a[i]/b[i]  也是分别递减的.
	for(p=L;p<=R;p++){
		if(s[p].id<=mid) temp[i++]= s[p];
		else temp[j++]= s[p];
	}
	for(i=L;i<=R;i++)s[i]= temp[i];
	Solve(L,mid);
	//维护左边的凸包,左区间已经按照x排好序.
	rear=0; 
	for(i=L; i<=mid; i++){
		while(rear>1 && slope(Q[rear],Q[rear-1])< slope(Q[rear-1],i)+eps) rear--;
		Q[++rear]= i;
	} 
	Q[++rear]=0; 
	// 用左区间更新右区间 
	for(i=mid+1, j=1 ;i<=R;i++){
		while(j<rear && slope(Q[j],Q[j+1])+eps > s[i].k) j++;
		//cout<< Q[j]<<" -> "<<s[i].id<<" "<<Q[j+1]<<" "<<s[i].k<<endl; 
		//s[Q[j]].put();
		f[s[i].id]= max(f[s[i].id], s[Q[j]].x*s[i].a+s[Q[j]].y*s[i].b);
	}
	Solve(mid+1,R);
	//按照 x值大小归并排序
	i=L; j=mid+1;
	for(p=L;p<=R && i<=mid && j<=R;p++){
		
		if(s[i].x<s[j].x || (fabs(s[i].x-s[j].x)<eps && s[i].y<s[j].y)) temp[p]= s[i++];
		else temp[p]= s[j++];
	} 
	while(i<=mid) temp[p++]= s[i++];
	while(j<=R) temp[p++]= s[j++];
	for(i=L;i<=R;i++) s[i]=temp[i];
}

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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值