JSOI 2008 Blue Mary开公司 题解

题目传送门

题目大意: n n n 次操作,每次操作有两种:1、增加一条直线;2、询问 x = T x=T x=T 时,在所有直线上 y y y 值的最大值。

题解

经典的李超线段树做法在这里,这篇博客讲另外一种cdq分治的做法。

脑补一下不难发现,最后对答案可能有贡献的那些直线,即处于最上方的那些直线,一定是一个下凸包的形状,像这样:
在这里插入图片描述
假如按时间顺序加入的直线的斜率递增,那么就可以很方便地维护出这个凸包,但题目并不保证这一点。

具体来说,一条直线能对一个询问产生贡献,需要满足: t i < t j t_i<t_j ti<tj

而我们希望直线按斜率递增的顺序出现,即 k i < k j k_i<k_j ki<kj

诶,那不是正好可以用cdq分治来做吗?

做法也很简单,将操作以时间顺序排好然后分治。每次递归完左右区间后,将左区间内的直线按斜率递增排序,然后一条一条加入单调队列。接着就可以很容易地更新右区间内的询问了。

当然,可以将右区间内的询问也进行排序,那么求解会变得更加简单,排序可以在分治时直接归并排序排好,时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

但是由于浮点数运算次数较多所以跑的并不算快,在吸了氧后跑到了 938 m s 938ms 938ms

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 100010
#define eps 1e-8

int n;
struct par{int type;double k,b;int pos;}a[maxn],tmp[maxn];
struct line{double k,b;}q[maxn];
int st,ed;
double meet(line x,line y){return (y.b-x.b)/(x.k-y.k);}
int dcmp(double x){return x>eps?1:x<-eps?-1:0;}
void cdq(int l,int r){
	if(l==r)return;
	int mid=l+r>>1;cdq(l,mid);cdq(mid+1,r);
	//求解
	q[st=ed=1]=(line){0,0};
	for(int i=l;i<=mid;i++)if(!a[i].type){
		line now=(line){a[i].k,a[i].b};
		while(ed>0&&dcmp(q[ed].k-now.k)==0&&dcmp(q[ed].b-now.b)<0)ed--;//特判斜率相同的直线
		while(ed>1&&dcmp(meet(q[ed],q[ed-1])-meet(q[ed],now))>0)ed--;
		q[++ed]=now;
	}
	for(int i=mid+1;i<=r;i++)if(a[i].type){
		while(st<ed&&a[i].k-meet(q[st],q[st+1])>eps)st++;
		a[i].b=max(a[i].b,q[st].b+q[st].k*a[i].k);
	}
	//归并
	int x=l,y=mid+1,now=0;
	while(x<=mid&&y<=r){
		if(dcmp(a[x].k-a[y].k)<0)tmp[++now]=a[x++];
		else tmp[++now]=a[y++];
	}
	while(x<=mid)tmp[++now]=a[x++];
	while(y<=r)tmp[++now]=a[y++];
	for(int i=l;i<=r;i++)a[i]=tmp[i-l+1];
}
bool cmp(par x,par y){return x.pos<y.pos;}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		char s[10];scanf("%s",s);
		if(s[0]=='P')a[i].type=0,scanf("%lf %lf",&a[i].b,&a[i].k),a[i].b-=a[i].k;
		else a[i].type=1,scanf("%lf",&a[i].k);
		a[i].pos=i;
	}
	cdq(1,n);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	if(a[i].type)printf("%d\n",(int)a[i].b/100);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值