bzoj-1492 货币兑换Cash (2)——CDQ分治

题意:

上一篇

题解:

方程还是那个方程f[i]=A[i] * X[j] + B[i] * Y[j];

化简为Y[i]=(-A[i]/B[i]) * X[i] + f[i]/B[i]这一坨;

既然这个斜率不单调,那排个序让它单调不就行了;

排序之后的问题就是,在i前面更新i的点不一定可以更新i,而应该用来更新i的点说不定还在i的后面;

那么这时候就是用CDQ分治解决;

经典的四步先贴上来:

1.将操作按照时间划分为两个子区间;

2.递归处理左区间的修改与询问;

3.用左区间的修改处理右区间的询问;

4.递归处理右区间的修改与询问;

光这么四句话肯定没用,下面是具体的;

动态规划中对f[i]的更新相当于是查询,而用f[i]来更新别人则相当于是一次修改;

那么在将所有的点按斜率排序之后,进行一个分治的solve(1,n),然后按四步走;

1.划分区间:

这里的时间就是天数,只需要取一个mid然后用mid把点分成两堆;

注意这里划分了以后,两个区间仍按斜率有序,并且左区间的全部时间都小于右区间的全部时间;

2.递归处理左区间:

递归下去要有一个边界,这里的边界显然就是l==r的时候;

这时这个结点前面的结点都已经对它更新了;

所以它在更新一下f[i-1]就是最终的f[i],顺便计算出X[i]Y[i]的值;

然后每层递归结束时要按X[i]排序(为了维护凸包方便)(这样的递归结构下用归并的线性显然比快拍要好);

3.用左区间修改右区间:

左区间已经按X[i]排序完成,可以扫一遍求出凸包;

右区间现在还是按斜率排序,直接上斜率优化;

这时候右区间的所有点已经被左区间的点处理完了;

4.递归处理右区间:

被左区间处理了的点还要被右区间在它前面的点处理,所以再递归搞一下;

然后就结束了,f[n]就是答案;

这些操作全都是线性复杂度的,而一共递归有logn层,复杂度为O(nlogn);

排完序之后的下标不是时间。。。sort写错的去看眼科大夫。。。

代码2k+,时间1232ms;

居然没有平衡树跑得快,但是代码上的确是省了不少;

代码:

#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 110000
#define which(x)	(tr[tr[x].fa].ch[1]==x)
const double INF = 1e100;
const double EPS = 1e-8;
using namespace std;
struct node
{
	double x, y, shope;
	int no;
}a[N], temp[N];
double f[N], A[N], B[N], R[N];
int st[N];
int cmp(node a, node b)
{
	return a.shope < b.shope;
}
double shope(int x, int y)
{
	if (fabs(a[x].x - a[y].x) < EPS)
		return a[x].y < a[y].y ? INF : -INF;
	else
		return (a[x].y - a[y].y) / (a[x].x - a[y].x);
}
void merge(int l, int r)
{
	memcpy(temp + l, a + l, sizeof(node)*(r - l + 1));
	int mid = (l + r) >> 1, i, j, k;
	for (k = l, i = l, j = mid + 1; k <= r; k++)
	{
		if (i <= mid&&j <= r)
			a[k] = temp[i].x < temp[j].x ? temp[i++] : temp[j++];
		else
			a[k] = (i == mid + 1 ? temp[j++] : temp[i++]);
	}
}
void slove(int l, int r)
{
	if (l == r)
	{
		f[l] = max(f[l], f[l - 1]);
		a[l].y = f[l] / (A[l] * R[l] + B[l]);
		a[l].x = R[l] * a[l].y;
	}
	else
	{
		int mid = (l + r) >> 1, i, j, k, top;
		for (i = l, j = l, k = mid + 1; i <= r; i++)
		if (a[i].no <= mid)
			temp[j++] = a[i];
		else
			temp[k++] = a[i];
		memcpy(a + l, temp + l, sizeof(node)*(r - l + 1));
		slove(l, mid);
		st[top = 1] = l;
		for (i = l + 1; i <= mid; i++)
		{
			while (top >= 2 && shope(st[top - 1], st[top]) < shope(st[top], i))
				top--;
			st[++top] = i;
		}
		for (i = mid + 1; i <= r; i++)
		{
			while (top >= 2 && shope(st[top - 1], st[top]) < a[i].shope)
				top--;
			f[a[i].no] = max(f[a[i].no], A[a[i].no] * a[st[top]].x + B[a[i].no] * a[st[top]].y);
		}
		slove(mid + 1, r);
		merge(l, r);
	}
}
int main()
{
	int n, i, j, k;
	double ans;
	scanf("%d%lf", &n, &f[1]);
	for (i = 1; i <= n; i++)
		scanf("%lf%lf%lf", A + i, B + i, R + i),
		a[i].shope = -A[i] / B[i],
		a[i].no = i;
	sort(a + 1, a + n + 1, cmp);
	slove(1, n);
	printf("%.3lf", f[n]);
	return 0;
}
时间: 2024-10-09 04:16:29

bzoj-1492 货币兑换Cash (2)——CDQ分治的相关文章

bzoj [NOI2007]货币兑换Cash (cdq分治+斜率优化 )

1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MB Submit: 2454  Solved: 1078 [Submit][Status][Discuss] Description Input 第一行两个正整数N.S,分别表示小Y 能预知的天数以及初始时拥有的钱数. 接下来N 行,第K 行三个实数AK.BK.RateK,意义如题目中所述 Output 只有一个实数MaxProfit,表示第N 天的操作结束时能够获得的最大的

【BZOJ】1492: [NOI2007]货币兑换Cash(cdq分治)

http://www.lydsy.com/JudgeOnline/problem.php?id=1492 蒟蒻来学学cdq神算法啊.. 详见论文 陈丹琦<从<Cash>谈一类分治算法的应用> orz 此题表示被坑精度.....导致没1a...开小号交了几发....................坑. 蒟蒻就说说自己的理解吧.. 首先这题神dp...(表示完全看不出来) 首先我们要最大化钱,那么可以将问题转化为最大化A券!(或B券)!!!!这点太神了,一定要记住这些!! 设d[i]表

[BZOJ 1492][NOI2007]货币兑换Cash(CDQ分治+斜率优化Dp)

Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个实数.每天随着市场的起伏波动, 两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目.我们记录第 K 天中 A券 和 B券 的 价值分别为 AK 和 BK(元/单位金券).为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法 .比例交易法分为两个方面:(a)卖出金券:顾客提

BZOJ 1492 货币兑换 cdq分治或平衡树维护凸包

题意:链接 方法:cdq分治或平衡树维护凸包 解析: 这道题我拒绝写平衡树的题解,我仅仅想说splay不要写挂,insert边界条件不要忘.del点的时候不要脑抽d错.有想写平衡树的去看140142或者留言我. 首先这道题能推出个表达式 f[i]代表第i天最大收益. xx[i]表示将第i天的钱都买A的数量 yy[i]表示将第i天的钱都买B的数量 所以f[i]=max(f[i?1],p[i].a?xx[j]+p[i].b?yy[j])j<i 所以我们要维护这个n^2的递推式 又知道f[i]是由小于

BZOJ 1176 Balkan 2007 Mokia CDQ分治

题目大意:有一些操作,给一个坐标代表的点加上一个数,和求出一个矩形中的所有数的和. 思路:一眼题,二位树状数组水过. ... .. . 哪里不对?W<=2000000.逗我?这n^2能开下? 这个时候CDQ神牛又来帮助我们了. 这个题应该算是CDQ分治的模板题了吧,简单分析一下,其实不难. 写这个题之前建议写一下BZOJ 1935 SHOI 2007 Tree 园丁的烦恼 树状数组这个题,是本题的简化版. 按照正常的解法,我们应该建立一个二位的数据结构,然后分别维护两维的信息.如果用动态开点的线

bzoj 2244: [SDOI2011]拦截导弹 cdq分治

2244: [SDOI2011]拦截导弹 Time Limit: 30 Sec  Memory Limit: 512 MBSec  Special JudgeSubmit: 237  Solved: 103[Submit][Status][Discuss] Description 某 国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度.并且能够拦截任意速度的导 弹,但是以后每一发炮弹都不能高于前一发的高度,其拦截的导弹的飞行速度

BZOJ 2244 SDOI2011 拦截导弹 CDQ分治/二维树状数组

题目大意:给定一个序列,每个元素是一个二元组,等概率选择一LIS,求LIS长度以及每个元素被选中的概率 第一问CDQ分治裸上 第二问用每个元素所在的LIS个数/总LIS个数就是答案 每个元素所在的LIS自己必选,然后统计前面的方案数和后面的方案数 以前面的方案数为例,令f[x]为以x结尾的LIS长度,那么有DP方程: g[i]=Σg[j] (f[j]+1=f[i],j<i,a[j].x<a[i].x,a[j].y<a[i].y) 将所有元素按f值排序,分层DP,每层DP是一个三维偏序,上

BZOJ 2244 [SDOI2011]拦截导弹 ——CDQ分治

三维偏序,直接CDQ硬上. 正反两次CDQ统计结尾的方案数,最后统计即可. #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define F(i,j,k) for (int i=j;i<=k;++i) #define D(i,j,k) for (int i=j;i>=i;--i) #define

BZOJ 2244: [SDOI2011]拦截导弹 [CDQ分治 树状数组]

传送门 题意:三维最长不上升子序列以及每个元素出现在最长不上升子序列的概率 $1A$了好开心 首先需要从左右各求一遍,长度就是$F[0][i]+F[1][i]-1$,次数就是$G[0][i]*G[1][i]$ 我们可以用一些转换来简化代码 反转之后变成$LIS$,然后再反转并且$x,y$取反还是$LIS$,写一遍就可以啦 然后本题的树状数组需要维护最大值以及最大值的数量,还有一个时间戳 #include <iostream> #include <cstdio> #include &