gmoj 7011. 2021.03.13【2021省赛模拟】nonintersect

本文介绍了一种使用分治算法解决线段非交叉匹配的问题,通过将线段投影到不同方向并进行匹配,确保投影后的线段总长度不变。利用最小权完美匹配的思想,结合欧几里得距离作为边权,确保了问题的可行性。文章讨论了随机选取角度和精确计算最大值两种方法,以及它们的时间复杂度分析。
摘要由CSDN通过智能技术生成

题目

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 π2XY 。如果有解,输出每条线段连接的端点;如果无解,输出 ORZ

1 ≤ n ≤ 5000 1\le n\le 5000 1n5000


题解

比赛时拿了 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=1nlicos(θix)=i=1nlisinθ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)=AcosxBsinx 。所以当 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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值