「题解」POI2005 AKC-Special Forces Manoeuvres


本文将同步发布于:

  • 洛谷博客;
  • csdn;
  • 简书。

题目

题目链接:洛谷 P3428、官网。

题意简述

给定 \(n\) 个圆 \((x_i,y_i,r_i)\),每个圆对应一个点集 \(S_i=\left\{(x,y)\mid (x-x_i)^2+(y-y_i)^2\leq r_i^2\right\}\)

求一个最小的 \(i\) 满足 \(\cap_{j=1}^i S_j=\varnothing\);如果无解输出 NIE

题解

简单又自然的随机化

我们考虑枚举 \(i\),然后判定 \(S_{1\sim i}\) 的交集是否为空。

如何判定呢?我们想到一个简单的方法,我们随机一些在圆的边界上的点,只需要判定这些点是否存至少在一个点在所有圆内即可。

这种方法简单又自然,但是随机化算法正确率不高,这远远不够。

研究几何性质

如果做计算几何题而抛弃几何性质,所得到的做法往往是劣解。

继续沿着上面的思路,我们同样考虑枚举 \(i\),然后判定 \(S_{1\sim i}\) 的交集是否为空。

不同的是,我们定义一个交集中横坐标最大的点为代表点(代表点只会有一个,这是因为圆是凸集,凸集的交集还是凸集)。

我们发现,如果一些圆的交集非空,那么其代表点一定满足:它是所有圆两两交集的代表中横坐标最小的那个。

证明十分显然,考虑交集的意义即可。

最后的结论

综上所述,对于一个 \(i\),我们只需要求出 \(1\sim i-1\)\(i\) 的代表点即可,如果所有代表点中横坐标最小的那一个在所有的圆内,那么其合法,否则不合法,换言之,答案为 \(i\)

我们考虑证明这个结论:

  • 若没有交集,则这个点必然不合法,符合我们的预期;
  • 若有交集,则我们需要证明这个点是交集的代表点。
    • 假设其不是交集的代表点,则交集的代表点可能在其左右;
    • 左边:不可能,若交集存在,则代表点的横坐标 \(\geq\) 当前点横坐标。
    • 右边:不可能,考虑当前点在 \(S_a\cap S_b\) 中得到,那么所有 \(x\geq\) 当前点横坐标的点均被交集抛弃,因此代表点的横坐标 \(\leq\) 当前点横坐标。
    • 由夹逼过程可知结论正确。

这个算法的时间复杂度为 \(\Theta(n^2)\)

参考程序

下面我们来解决两圆求交的问题。

下面介绍一下两种方法:余弦定理和相似三角形。

余弦定理

用余弦定理求解需要用到三角函数,常数大,精度差。

我们考虑下图:

2WRtN4.png

\(\triangle{ACB}\) 运用余弦定理,得到 \(r_a^2+d^2-2dr_a\cos\alpha=r_b^2\),进而求出 \(\alpha=\arccos\left(\frac{r_a^2+d^2-r_b2}{2dr_a}\right)\)

然后我们再求出基准角 \(\beta\),显然 \(\beta=\texttt{atan2}(y_b-y_a,x_b-x_a)\)

因此,我们得到了 \(C,D\) 两点的对 \(A\) 的极角为 \(\beta+\alpha\)\(\beta-\alpha\)

对于极角为 \(\theta\),极径为 \(r_a\) 的点,我们得出其对应点的坐标为 \((r_a\cos\theta,r_a\sin\theta)\)

#include
using namespace std;
#define reg register
typedef long long ll;

const double eps=1e-6;

inline int sgn(reg double x){
	if(fabs(x)=0;
	}
	inline Point getRig(void)const{
		return o+Vector(r,0);
	}
};

inline bool isCon(const Circle& a,const Circle& b){
	return sgn(sqr(a.r-b.r)-getDis2(a.o,b.o))>=0;
}

inline bool isSep(const Circle& a,const Circle& b){
	return sgn(getDis2(a.o,b.o)-sqr(a.r+b.r))>0;
}

inline Point getPot(const Circle &a,const Circle &b){
	if(isCon(a,b))
		if(sgn(b.getRig().x-a.getRig().x)>0)
			return a.getRig();
		else
			return b.getRig();
	else if(isSep(a,b))
		return Point(nan(""),nan(""));
	else{
		if(a.contain(b.getRig()))
			return b.getRig();
		else if(b.contain(a.getRig()))
			return a.getRig();
		else{
			reg double d=getDis(a.o,b.o);
			reg double ang=acos(((sqr(a.r)+sqr(d))-sqr(b.r))/(2*a.r*d));
			reg double delta=atan2(b.o.y-a.o.y,b.o.x-a.o.x);
			reg double ang1=delta+ang,ang2=delta-ang;
			Point p1=a.o+Vector(cos(ang1)*a.r,sin(ang1)*a.r);
			Point p2=a.o+Vector(cos(ang2)*a.r,sin(ang2)*a.r);
			Point res;
			if(sgn(p2.x-p1.x)>0)
				res=p2;
			else
				res=p1;
			return res;
		}
	}
}

int n;
Circle a[MAXN];

int main(void){
	scanf("%d",&n);
	Point lef(0,0);
	for(reg int i=1;i<=n;++i){
		static int x,y,r;
		scanf("%d%d%d",&x,&y,&r);
		a[i].o=Point(x,y),a[i].r=r;
		if(i==2)
			lef=getPot(a[1],a[2]);
		else if(i>2){
			for(reg int j=1;j

相似三角形

2WRN4J.png

如上图,我们设 \(a=|AG|\)\(b=|BG|\)\(h=|CG|\)

那么我们有:

\[\begin{cases}r_a^2=a^2+h^2\\r_b^2=b^2+h^2\\a+b=d\end{cases} \]

那么我们有:

\[a=\frac{r_a^2+d^2-r_b^2}{2d} \]

然后考虑 \(\triangle AIB\sim\triangle CHG\),我们有:

\[(y_b-y_a)h=d(y_c-y_g) \]

我们可由此解出坐标,其他同理可算出。

#include
using namespace std;
#define reg register
typedef long long ll;

const double eps=1e-6;

inline int sgn(reg double x){
	if(fabs(x)=0;
	}
	inline Point getRig(void)const{
		return o+Vector(r,0);
	}
};

inline bool isCon(const Circle& a,const Circle& b){
	return sgn(sqr(a.r-b.r)-getDis2(a.o,b.o))>=0;
}

inline bool isSep(const Circle& a,const Circle& b){
	return sgn(getDis2(a.o,b.o)-sqr(a.r+b.r))>0;
}

inline Point getPot(const Circle &a,const Circle &b){
	if(isCon(a,b))
		if(sgn(b.getRig().x-a.getRig().x)>0)
			return a.getRig();
		else
			return b.getRig();
	else if(isSep(a,b))
		return Point(nan(""),nan(""));
	else{
		if(a.contain(b.getRig()))
			return b.getRig();
		else if(b.contain(a.getRig()))
			return a.getRig();
		else{
			reg double d=getDis(a.o,b.o);
			reg double val=(sqr(a.r)+sqr(d)-sqr(b.r))/(2*d);
			reg double h=sqrt(sqr(a.r)-sqr(val));
			Point bas=a.o+(b.o-a.o)*(val/d);
			Vector tmp=Vector(b.o.y-a.o.y,a.o.x-b.o.x)*(h/d);
			Point p1=bas-tmp,p2=bas+tmp;
			if(sgn(p2.x-p1.x)>0)
				return p2;
			else
				return p1;
		}
	}
}

int n;
Circle a[MAXN];

int main(void){
	scanf("%d",&n);
	Point lef(0,0);
	for(reg int i=1;i<=n;++i){
		static int x,y,r;
		scanf("%d%d%d",&x,&y,&r);
		a[i].o=Point(x,y),a[i].r=r;
		if(i==2)
			lef=getPot(a[1],a[2]);
		else if(i>2){
			for(reg int j=1;j