题目
https://gmoj.net/senior/#main/show/7011
题目大意
给你
n
n
n 条可能相交的线段,你要在这
2
n
2n
2n 个端点之间重新连线,使得连完之后的线段互不相交。记原线段的总长度为
Y
Y
Y ,连完之后的线段的总长度为
X
X
X ,那么还要满足
2
π
X
≤
Y
\frac{2}{\pi}X\le Y
π2X≤Y 。如果有解,输出每条线段连接的端点;如果无解,输出 ORZ
。
1 ≤ n ≤ 5000 1\le n\le 5000 1≤n≤5000
题解
比赛时拿了 3 3 3 分就走人。这题就连 15 15 15 分都不会。
相信大家都会觉得这个 2 π \frac{2}{\pi} π2 比较奇怪。其实它就是 ∣ cos x ∣ |\cos{x}| ∣cosx∣ 的期望,即一条线段在某个方向上的投影长度除以原长度的期望。
为了搞清楚这个结论,我向数竞大佬 W Z T WZT WZT 请教了求导和定积分的知识。
假设我们把所有的点都投影到与平面直角坐标系水平方向夹角为 θ \theta θ 的 数轴 上,那么投影后的线段总长度除以原长度的期望是 2 π \frac{2}{\pi} π2 。我们要想办法让最后得到的线段总长度不变。那么对于一条线段,将其投影后坐标较小者染为黑色,坐标较大者染为白色。然后让黑色点匹配白色点,并且要满足不相交的条件。如果能找到一个合法匹配方案,那么就找到解了。
怎么匹配呢?假设现在有两条线段 A B , C D AB,CD AB,CD 相交:
可以发现 A B + C D = A P + B P + C P + D P > A D + B C AB+CD=AP+BP+CP+DP>AD+BC AB+CD=AP+BP+CP+DP>AD+BC ,因此我们把所有的边权都定为两点之间的 欧几里得距离 ,然后跑 最小权完美匹配 即可。
由于 最小权完美匹配 一定存在,因此本题一定有解。
但是这个匹配用传统的做法—— 网络流 来做会 T 飞。
题解提供了一种很好的思路:假设现在我们要给一个点集跑 最小权完美匹配 ,可以考虑分治,即每次取出两个点使得它们之间的连线将点集切割成黑、白点数量相同的两个部分。具体实现的话就是取出这个点集中最左边的那个点,然后把其它点 极角排序 ,依次扫过去。根据上面的证明,只要找到任意一组最小权完美匹配即可。
这样每次匹配的期望时间复杂度是 O ( n log 2 2 n ) O(n\log_2^2 n) O(nlog22n) ,最坏时间复杂度是 O ( n 2 log 2 n ) O(n^2\log_2 n) O(n2log2n) 。
但是怎么找出这个 θ \theta θ 呢?直接随机就好了,可以证明每一次有解的概率是 1 2 \frac{1}{2} 21 。
如果不想随机也可以做,不过时间会劣一些。
令第
i
i
i 条线段与水平方向的夹角为
θ
i
\theta_i
θi ,长度为
l
i
l_i
li ,那么
X
=
∑
i
=
1
n
l
i
∣
cos
(
θ
i
−
x
)
∣
=
∑
i
=
1
n
l
i
∣
sin
θ
i
sin
x
+
cos
θ
i
cos
x
∣
\begin{aligned} X&=\sum_{i=1}^n l_i|\cos{(\theta_i -x)}|\\ &=\sum_{i=1}^n l_i|\sin{\theta_i}\sin{x} + \cos{\theta_i}\cos{x} | \end{aligned}
X=i=1∑nli∣cos(θi−x)∣=i=1∑nli∣sinθisinx+cosθicosx∣
可以发现这个对于某一个
i
i
i ,它的绝对值号会在
tan
x
=
−
1
tan
θ
i
\tan{x}=-\frac{1}{\tan{\theta_i}}
tanx=−tanθi1 的时候改变。
可以发现这个函数就是一个 2 n 2n 2n 段的分段函数,它的每一段都可以写成 f ( x ) = A sin x + B cos x f(x)=A\sin{x}+B\cos{x} f(x)=Asinx+Bcosx 的形式,它的导函数为 f ′ ( x ) = A cos x − B sin x f'(x)=A\cos{x} - B\sin{x} f′(x)=Acosx−Bsinx 。所以当 tan x = A B \tan{x}=\frac{A}{B} tanx=BA 时会取到最大值,如果取不到这样的 x x x ,那么 f ( x ) max = max ( f ( l ) , f ( r ) ) , x ∈ [ l , r ] f(x)_{\max}=\max{(f(l),f(r))},x\in[l,r] f(x)max=max(f(l),f(r)),x∈[l,r]。
期望时间复杂度 O ( n + n log 2 2 n ) O(n+n\log^2_2 n) O(n+nlog22n) ,最坏时间复杂度 O ( n + n 2 log 2 n ) O(n+n^2\log_2 n) O(n+n2log2n) 。
代码
#include<cmath>
#include<ctime>
#include<cstdio>
#include<random>
#include<algorithm>
using namespace std;
#define fo(i,l,r) for(i=l;i<=r;++i)
#define N 10005
typedef long double LF;
const LF pi=3.14159265359;
struct node{int x,y,id;LF deg;bool type;}A[N],tmp_p,tmp_q;
bool cmp1(node x,node y){return x.id<y.id;}
bool cmp2(node x,node y){return x.deg<y.deg;}
inline LF sqr(LF x){return x*x;}
LF theta,lim;
int n,m,s,match[N][2],line[N][2];
LF solve(int n,node a[])
{
if(!n) return 0;
// fo(i,1,n) printf("%d\t",a[i].id);
// puts("\n");
if(n==2)
{
match[++s][0]=a[1].id,match[s][1]=a[2].id;
return sqrt(sqr(a[1].x-a[2].x)+sqr(a[1].y-a[2].y));
}
int i,p=1,q,cnt=0,l=0,r=0;
LF dis;
fo(i,2,n)
if(a[i].x<a[p].x||a[i].x==a[p].x&&a[i].y<a[p].y) p=i;
if(p>1) swap(a[1],a[p]);
fo(i,2,n) a[i].deg=atan2(a[i].x-a[1].x,a[i].y-a[1].y);
sort(a+2,a+n+1,cmp2);
fo(i,2,n)
{
if(a[i].type!=a[1].type&&!cnt){q=i;break;}
a[i].type?++cnt:--cnt;
}
match[++s][0]=a[1].id,match[s][1]=a[q].id;
dis=sqrt(sqr(a[q].x-a[1].x)+sqr(a[q].y-a[1].y));
l=q-2,r=n-q;
tmp_p=a[1],tmp_q=a[q];
a[1]=a[q-1],a[q-1]=a[n-1],a[q]=a[n];
a[n-1]=tmp_p,a[n]=tmp_q;
return solve(l,a)+solve(r,a+l)+dis;
}
inline bool judge()
{
int i;
LF l,r;
sort(A+1,A+m+1,cmp1);
fo(i,1,n)
{
l=A[line[i][0]].x*cos(theta)+A[line[i][0]].y*sin(theta),
r=A[line[i][1]].x*cos(theta)+A[line[i][1]].y*sin(theta);
if(l<r) A[line[i][0]].type=0,A[line[i][1]].type=1;
else A[line[i][0]].type=1,A[line[i][1]].type=0;
}
s=0;
LF ans=solve(m,A);
return ans<lim;
}
int main()
{
freopen("nonintersect.in","r",stdin);
freopen("nonintersect.out","w",stdout);
LF sum=0;
int i;
scanf("%d",&n),m=n<<1;
fo(i,1,m) scanf("%d%d",&A[i].x,&A[i].y),A[i].id=i;
fo(i,1,n) scanf("%d%d",&line[i][0],&line[i][1]);
fo(i,1,n) sum+=sqrt(sqr(A[line[i][0]].x-A[line[i][1]].x)+sqr(A[line[i][0]].y-A[line[i][1]].y));
lim=sum*2/pi;
default_random_engine e;
e.seed((unsigned)time(0));
uniform_real_distribution<LF>u(0,pi);
while(theta=u(e),judge());
fo(i,1,n) printf("%d %d\n",match[i][0],match[i][1]);
return 0;
}