【ZJOI2019】浙江省选(半平面交)(二分)

传送门


题解:

首先这道题是ZJOI2019里面idea最简单的题。

注意是idea最简单的题,也就是说六道题口胡起来最简单的是这道。那这道题凭什么放在D2T3?

凭它细节巨多。。。

来,先口胡正解做法。

按照斜率给所有直线排序,考虑从答案小的往答案大的处理,把已经得到答案的直线跳过剩下的直线拿来做半平面交,然后枚举所有已经得到答案的直线,考虑 x x x处于什么区间的时候这条直线会让下面直线的排名+1,记录所有变化位置,扫描线,更新答案。

没了,思路就是这么简单,你让我再把思路讲详细点我也不知道该怎么讲。如果看不懂说明你的计算几何水平还没到能做这道题的程度

然后是细节。

首先是做半平面交时候的细节。

第一个细节,当交成下面这种情况的时候,三条线都要保留:

在这里插入图片描述
因为题目中排名的计算方式是“严格大于”,所以等于的情况不能够影响它的排名,但是按照一般半平面交的写法,这种情况是要弹掉的。。。

然后你看了一眼数据范围,这是一个开long double都会炸精度的范围,你并没有什么好的办法来处理这种情况。。。

于是你手写一个分数类,然后你发现它乘法爆long long 了,所以分数我们只能表示成 a + b / c a+b/c a+b/c的形式。。。

第二个细节,交成下面这种情况中间那条线是要弹掉的:

在这里插入图片描述

注意这里实际上说的是中间那条线不能够在 x x x为任何一个整数的时候成为最大值。

但是本题要求的 x x x必须是一个非负整数,所以中间那条线现在已经就宣告gg了

既然要求 x x x是一个非负整数,那么新的直线与原来直线的交点到了 x x x负半轴是需要把栈弹完的。

其实不容易注意到的细节就前面两个,剩下的就是二分找到之前已经确定了答案的直线的影响范围。这个其实还好,注意一下边界,根据选择记录开区间还是闭区间,细节有所不同也不是很好说清楚,只有自己注意了。

具体实现不建议用任何实数类型,其实封装一个简易的带分数类型是可以做到的,因为只有赋值和比较,没有带分数之间的运算。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return p1==p2&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
	inline ll gl(){return get<ll>();}
}
using namespace IO;

using std::cerr;
using std::cout;
using pli=std::pair<ll,int>;
#define fi first
#define se second

cs int N=1e5+7;

struct frac{
	ll a,b,c;//a + b/c (c>0,b>0)
	frac(){a=b=0,c=1;}
	frac(ll x,ll y){
		if(y<0)x=-x,y=-y;
		a=x/y,c=y,b=x%y;
		if(b<0)b+=c,--a;
	}
	ll flr()cs{return a;}
	ll cel()cs{return a+(b>0);}
	bool operator<(cs frac &x)cs{return a==x.a?b*x.c<x.b*c:a<x.a;}
	bool operator<=(cs frac &x)cs{return a==x.a?b*x.c<=x.b*c:a<=x.a;}
};

int n,m,ct,tp;
int id[N],st[N],ans[N];
ll a[N],b[N];frac x[N];
pli q[N<<1];

inline frac gx(int x,int y){
	return frac(b[x]-b[y],a[y]-a[x]);
}
inline void solve(int k){
	ct=tp=0;x[0]=frac(0,1);
	for(int re i=1;i<=n;++i)if(-1==ans[id[i]]&&a[id[i]]>a[st[tp]]){
		int u=id[i];
		while(tp&&gx(u,st[tp]).flr()<x[tp].cel())--tp;
		st[++tp]=u;if(tp>1)x[tp]=gx(u,st[tp-1]);
	}
	x[tp+1]=frac(1ll<<60,1);
	for(int re i=1;i<=n;++i)if(~ans[i]){
		int l=1,r=tp-1,t=tp;
		while(l<=r){
			int mid=l+r>>1;
			if(a[st[mid]]>=a[i]||gx(st[mid],i)<=x[mid+1])t=mid,r=mid-1;
			else l=mid+1;
		}
		q[++ct]=pli(a[st[t]]>=a[i]?0ll:gx(st[t],i).flr()+1,1);
		l=2,r=tp,t=1;
		while(l<=r){
			int mid=l+r>>1;
			if(a[st[mid]]<=a[i]||x[mid]<=gx(st[mid],i))t=mid,l=mid+1;
			else r=mid-1;
		}
		if(a[st[t]]>a[i])q[++ct]=pli(gx(st[t],i).cel(),-1); 
	}
	std::sort(q+1,q+ct+1);
	for(int re i=1,j=1,p=0;i<=tp;++i){
		while(j<=ct&&q[j].fi<=x[i].cel())p+=q[j++].se;
		if(p<k)ans[st[i]]=k;
		while(j<=ct&&q[j].fi<=x[i+1].flr()){
			int l=j;while(l<=ct&&q[l].fi==q[j].fi)p+=q[l++].se;
			if(p<k)ans[st[i]]=k;j=l;
		}
	}
}

signed main(){
#ifdef zxyoi
	freopen("ZJOI.in","r",stdin);
#endif
	n=gi(),m=gi();
	for(int re i=1;i<=n;++i)
	a[i]=gl(),b[i]=gl(),id[i]=i,ans[i]=-1;
	std::sort(id+1,id+n+1,[](int x,int y){
		return a[x]<a[y]||(a[x]==a[y]&&b[x]>b[y]);
	});
	for(int re i=1;i<=m;++i)solve(i);
	for(int re i=1;i<=n;++i)cout<<ans[i]<<" ";
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值