CF1446FLine Distance【计算几何,树状数组,二分】

正题

题目链接:https://www.luogu.com.cn/problem/CF1446F


题目大意

给出\(n\)个点,求所有点对构成的直线中与原点距离第\(k\)小的距离

\(2\leq n\leq 10^5,1\leq k\leq \frac{n(n-1)}{2}\)


解题思路

二分还是挺显然的,考虑二分了之后怎么判断一个距离以内的直线数量

两个点对之间的直线在原点距离\(d\)以内,也就是这条直线经过原点为中心半径为\(d\)的圆。换一种理解,也就是如果有这个圆那么这两个点对之间无法相互看见。

现在问题变为了求有多少个点对之间无法相互看见,给张官方题解的图片就挺容易理解的
在这里插入图片描述
也就是说我们对于每个点找到它与圆的两个切点之间在圆上构成的一个区间。

如果两个点的区间有交集那么他们就可以相互看见。

现在问题就变为了求有多少对区间有交。

这个直接用树状数组统计就好了。

环的话直接断开,跨越了环的就取反。


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define lowbit(x) (x&-x)
using namespace std;
const ll N=2e5+10;
double eps=1e-8,Pi=acos(-1);
ll n,k,cnt,m,t[N],p[N],l[N],r[N];
double a[N],b[N],c[N],L[N],R[N];
bool v[N];
void Change(ll x,ll val){
	while(x<=cnt){
		t[x]+=val;
		x+=lowbit(x);
	}
	return;
}
ll Ask(ll x){
	ll ans=0;
	while(x){
		ans+=t[x];
		x-=lowbit(x);
	}
	return ans;
}
bool cmp(ll x,ll y)
{return l[x]<l[y];}
ll check(double d){
	cnt=0;m=0;
	for(ll i=1;i<=n;i++)v[i]=0;
	for(ll i=1;i<=n;i++){
		double dis=sqrt(a[i]*a[i]+b[i]*b[i]);
		if(dis-d<eps){v[i]=1;continue;}
		double ang=atan2(b[i],a[i]),ta=acos(d/dis);
		L[i]=ang-ta;R[i]=ang+ta;
		if(L[i]<-Pi)L[i]+=2*Pi;
		if(R[i]>Pi)R[i]-=2*Pi;
		if(L[i]>R[i])swap(L[i],R[i]);
		c[++cnt]=L[i];c[++cnt]=R[i]; 
	}
	sort(c+1,c+1+cnt);
	for(ll i=1;i<=n;i++){
		if(v[i])continue;p[++m]=i;
		l[i]=lower_bound(c+1,c+1+cnt,L[i])-c;
		r[i]=lower_bound(c+1,c+1+cnt,R[i])-c;
	}
	memset(t,0,sizeof(t));
	sort(p+1,p+1+m,cmp);
	ll ans=n*(n-1)/2;
	for(ll i=1;i<=m;i++){
		ll x=p[i];
		ans-=Ask(r[x])-Ask(l[x]-1);
		Change(r[x],1);
	}
	return ans; 
}
signed main()
{
	scanf("%lld%lld",&n,&k);
	for(ll i=1;i<=n;i++)
		scanf("%lf%lf",&a[i],&b[i]);
	double l=0,r=20000;
	while(r-l>=eps){
		double mid=(l+r)/2.0;
		if(check(mid)<k)l=mid;
		else r=mid;
	}
	printf("%.10lf",l);
	return 0;
}
原文地址:https://www.cnblogs.com/QuantAsk/p/14618765.html