若干个点,两两间连线。问和原点距离第(k)大的直线,到原点距离是多少。
(nle 10^5)
显然二分,然后就是计数。
结论:两个点(A,B)分别引切线到圆,分别记切点为(A_0,A_1,B_0,B_1)。如果(A_0A_1,B_0B_1)相交,那么(AB)与圆相离。
画画图即可证明。
离散化之后树状数组即可。注意判断一下点在圆内的情况。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cassert>
#define N 100005
#define ll long long
const double PI=acos(-1);
int n;
ll k;
struct DOT{double x,y;} d[N];
double sqr(double x){return x*x;}
void adjust(double &t){
t-=int(t/(2*PI))*(2*PI);
if (t<-1e-10) t+=2*PI;
}
int m;
DOT q[N];
int nv;
double *p[N*2];
bool cmpp(double *x,double *y){return *x<*y;}
int t[N*2];
void add(int x){
for (;x<=nv;x+=x&-x)
t[x]++;
}
int query(int x){
int r=0;
for (;x;x-=x&-x)
r+=t[x];
return r;
}
void init(int n){
sort(p+1,p+n+1,cmpp);
nv=0;
double last=19260817;
for (int i=1;i<=n;++i){
if (*p[i]!=last)
last=*p[i],++nv;
*p[i]=nv;
}
memset(t,0,sizeof(int)*(nv+1));
}
bool cmpq(DOT a,DOT b){return a.y<b.y;}
void insert(double l,double r){
adjust(l);
adjust(r);
if (l>r) swap(l,r);
q[++m]={l,r};
}
ll res;
ll calc(double r){
m=0;
for (int i=1;i<=n;++i){
double l=sqrt(sqr(d[i].x)+sqr(d[i].y));
if (l<=r) continue;
double b=acos(r/l);
double a=atan2(d[i].y,d[i].x);
insert(a-b,a+b);
}
res=(ll)(n-m)*m+(ll)(n-m)*(n-m-1)/2;
sort(q+1,q+m+1,cmpq);
for (int i=1;i<=m;++i)
p[i]=&q[i].x,p[m+i]=&q[i].y;
// for (int i=1;i<=m;++i)
// printf("%lf %lf
",q[i].x,q[i].y);
init(m*2);
// for (int i=1;i<=m;++i)
// printf("%d %d
",(int)q[i].x,(int)q[i].y);
for (int i=m;i>=1;--i){
res+=query((int)q[i].x)+(m-i)-query((int)q[i].y);
add((int)q[i].x);
}
return res;
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d%lld",&n,&k);
for (int i=1;i<=n;++i)
scanf("%lf%lf",&d[i].x,&d[i].y);
// printf("%lld
",calc(1));
// return 0;
// return 0;
double l=0,r=2e4;
while (r-l>1e-9){
double mid=(l+r)/2;
// printf("%lld
",calc(mid));
if (calc(mid)>=k)
r=mid;
else
l=mid;
}
printf("%.8lf
",l);
return 0;
}