BZOJ1492【NOI2007】货币兑换

3 篇文章 0 订阅
3 篇文章 0 订阅

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1492


【分析】

    首先要利用一个贪心的思想,就是在同一天要么把钱全部换成金劵,要么把金劵全部换成钱。

    于是设f[i]为第i天能得到的最多的钱,x[i]与y[i]分别表示用第i天换来的钱全部换取A、B劵的数量,那么有f[i]=max{a[i]*x[j]+b[i]*y[j]}(j<i)。

    这是一个典型的可以斜率优化的式子。对于当前状态i,有决策j和k,若a[i]*x[j]+b[i]*y[j]>a[i]*x[k]+b[i]*y[k]则决策j优于k。

    不妨设x[j]<x[k],上式可转化为(y[j]-y[k])/(x[j]-x[k])<-a[i]/b[i]。

    然而我们发现,x是不单调的,-a[i]/b[i]也是不单调的。

    平衡树维护!超长代码......

    我们可以用CDQ分治!(CDQ大法好)

    因为状态i只由小于i的决策转移得到,且决策转移只与其x和y的值有关,与它的位置无关,所以决策1..i-1顺序任意。不妨将决策1..i-1按x与y为关键字排序。

    此时如果状态i..n的-a[i]/b[i]值是单调的,那么就可以对1..i-1维护一个凸包,用凸包上的决策更新状态i..n,时间O(n)。

    于是可以每次把序列二分处理。在最开始将每一天的操作按-a[i]/b[i]排序,然后Solve(1,n)。

    利用归并排序将天数(id)1..mid的操作全部放在左边,mid+1..n的操作放右边。在Solve的最后将区间内的决策按x,y排序,这样就可以O(n)维护凸包。然后利用第1..mid天的决策更新天数mid+1..n的最优值。


【代码】

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=100010;
const double eps=1e-8;

struct Node
{
	int id;
	double a,b,x,y,k,rate;
}P[maxn],T[maxn],*Q[maxn];

double f[maxn];
int n;

bool cmpk(const Node &a,const Node &b) {return a.k>b.k;}
bool cmpx(const Node &a,const Node &b) {return a.x<b.x || (fabs(a.x-b.x)<eps && a.y<b.y);}
double K(Node *a,Node *b) {if (fabs(a->x-b->x)<eps) return 1e50;return (a->y-b->y)/(a->x-b->x);}

void Update(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].rate+P[l].b);
		P[l].x=P[l].y*P[l].rate;
		return;
	}
	int top=0,head=0,mid=(l+r)>>1;
	Q[0]=&P[0];
	for (int i=l;i<=mid;i++)
	{
		while (top && K(&P[i],Q[top])>K(Q[top],Q[top-1])) top--;
		Q[++top]=&P[i];
	}
	for (int i=mid+1;i<=r;i++)
	{
		int x=P[i].id;
		while (head<top && K(Q[head+1],Q[head])>P[i].k) head++;
		f[x]=max(f[x],Q[head]->x*P[i].a+Q[head]->y*P[i].b);
	}
}

void Solve(int l,int r)
{
	if (l==r) {Update(l,r);return;}
	int p1,p2,mid=(l+r)>>1;
	p1=l;p2=mid+1;
	for (int i=l;i<=r;i++) (P[i].id<=mid)?T[p1++]=P[i]:T[p2++]=P[i];
	for (int i=l;i<=r;i++) P[i]=T[i];
	Solve(l,mid);
	Update(l,r);
	Solve(mid+1,r);
	p1=l;p2=mid+1;
	for (int i=l;i<=r;i++) (p2>r || (cmpx(P[p1],P[p2]) && p1<=mid))?T[i]=P[p1++]:T[i]=P[p2++];
	for (int i=l;i<=r;i++) P[i]=T[i];
}

void Init()
{
	scanf("%d%lf",&n,&f[0]);
	for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&P[i].a,&P[i].b,&P[i].rate),P[i].k=-P[i].a/P[i].b,P[i].id=i;
	sort(P+1,P+1+n,cmpk);
}

int main()
{
	Init();
	Solve(1,n);
	printf("%.3f\n",f[n]);
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值