7011. 2021.03.13【2021省赛模拟】nonintersect
平面上有若干条线段,设其总长为\(X\)。现在需要把这些线段拆开,点两两匹配成为新的线段,要求这些线段不相交。设其总长为\(Y\),需要满足\(\frac{2}{\pi}X\le Y\)。
\(n\le 5000\)
考虑\(\frac{2}{\pi}\)是什么。随机一条直线的倾斜角,一条线段在这个直线上投影的长度和自身长度的比为\(|\cos\theta|\),积分一下得到期望\(\frac{2}{\pi}\)。
根据期望的线性性,随机这条直线之后,所有线段的投影的期望长度和为\(\frac{2}{\pi}X\)。
于是必然可以找到一个角度\(\theta\),使得所有线段的投影的期望长度大于等于这个值。(由期望的定义得。否则期望会更小)
假设找到了这个角度,把点投影到这条线段上,把前后\(n\)个点分为黑点和白点,互相匹配。匹配之后,投影和的长度肯定大于等于\(\frac{2}{\pi}X\)。
不考虑相交时,显然存在完美匹配。现在把边权设为欧几里得距离,此时最小权完美匹配一定无交。于是证明了无交的完美匹配一定存在。
选择一个\(x\)坐标最小的点(如果相同\(y\)坐标最小)拉出来,对其它点极角排序,找到一个和这个点颜色相异的,并且两点连线之后把点集分开成两个黑白点个数相同的点集的点,分治下去做。这样最坏情况下\(O(n^2\lg n)\)。
问题是怎么找到\(\theta\):可以发现投影和的长度是关于倾斜角的分段函数,有\(O(n)\)段,每段是\(a\cos x+b \sin x\)的形式。在每段找出极值即可,最终取最大的即可。这一部分时间\(O(n\lg n)\)(段间的分界点需要排序)
using namespace std;
#include <bits/stdc++.h>
#define N 10005
#define fi first
#define se second
#define mp(x,y) make_pair(x,y)
const double PI=acos(-1);
int n;
struct DOT{double x,y;} d[N];
DOT operator+(DOT a,DOT b){return {a.x+b.x,a.y+b.y};}
DOT operator-(DOT a,DOT b){return {a.x-b.x,a.y-b.y};}
double dot(DOT a,DOT b){return a.x*b.x+a.y*b.y;}
double cro(DOT a,DOT b){return a.x*b.y-a.y*b.x;}
double len(DOT a){return sqrt(dot(a,a));}
pair<int,int> ed[N];
double le[N],theta[N];
int m;
pair<double,int> o[N];
int sign[N];
double a,b;
double ans,anss;
double calc(double x){return a*cos(x)+b*sin(x);}
void upd(double l,double r){
if (calc(l)>anss) anss=calc(l),ans=l;
if (calc(r)>anss) anss=calc(r),ans=r;
double t=(a!=0?atan(b/a):PI/2);
if (l<t && t<r && calc(t)>anss) anss=calc(t),ans=t;
}
int c[N];
pair<double,int> p[N];
bool cmpang(pair<double,int> a,pair<double,int> b){
return a.fi<b.fi || a.fi==b.fi && d[a.se].x<d[b.se].x;
}
pair<int,int> ls[N];
int nl;
void divide(int q[],int n){
if (n==0)
return;
int v=q[1];
for (int i=2;i<=n;++i)
if (d[q[i]].x<d[v].x || d[q[i]].x==d[v].x && d[q[i]].y<d[v].y)
v=q[i];
int k=0;
for (int i=1;i<=n;++i)
if (q[i]!=v){
DOT u=d[q[i]]-d[v];
p[++k]=mp(atan2(u.y,u.x),q[i]);
}
sort(p+1,p+k+1,cmpang);
int s=c[v];
for (int i=1;i<=k;++i){
s+=c[p[i].se];
if (s==0){
ls[++nl]=mp(v,p[i].se);
for (int j=1;j<=k;++j)
q[j]=p[j].se;
divide(q,i-1);
divide(q+i,n-i-1);
return;
}
}
}
void doit(){
DOT v={cos(ans),sin(ans)};
for (int i=1;i<=n*2;++i)
p[i]=mp(dot(d[i],v),i);
sort(p+1,p+n*2+1);
for (int i=1;i<=n;++i) c[p[i].se]=1;
for (int i=n+1;i<=n*2;++i) c[p[i].se]=-1;
static int q[N];
for (int i=1;i<=n*2;++i)
q[i]=i;
divide(q,n*2);
}
int main(){
// freopen("nonintersect.in","r",stdin);
// freopen("nonintersect.out","w",stdout);
freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n*2;++i)
scanf("%lf%lf",&d[i].x,&d[i].y);
for (int i=1;i<=n;++i){
int p,q;
scanf("%d%d",&p,&q);
DOT v=d[p]-d[q];
le[i]=len(v);
theta[i]=atan2(v.y,v.x);
if (theta[i]<0)
theta[i]+=PI;
if (theta[i]<PI/2){
o[++m]=mp(theta[i]+PI/2,i);
sign[i]=1;
}
else{
o[++m]=mp(theta[i]-PI/2,i);
sign[i]=-1;
}
a+=le[i]*cos(theta[i])*sign[i];
b+=le[i]*sin(theta[i])*sign[i];
}
sort(o+1,o+m+1);
o[0].fi=0;
for (int i=1;i<=n;++i){
upd(o[i-1].fi,o[i].fi);
sign[o[i].se]*=-1;
a+=le[o[i].se]*cos(theta[o[i].se])*sign[o[i].se]*2;
b+=le[o[i].se]*sin(theta[o[i].se])*sign[o[i].se]*2;
}
upd(o[n].fi,PI);
doit();
for (int i=1;i<=nl;++i)
printf("%d %d\n",ls[i].fi,ls[i].se);
return 0;
}