1. 程式人生 > 其它 >「題解」POI2005 AKC-Special Forces Manoeuvres

「題解」POI2005 AKC-Special Forces Manoeuvres

本文將同步釋出於:

題目

題目連結:洛谷 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)\)

參考程式

參考程式中選擇運用餘弦定理來解決兩圓求交的問題,常數較大;實際上有更加優秀的方法,可去除三角函式運算。

#include<bits/stdc++.h>
using namespace std;
#define reg register
typedef long long ll;

const double eps=1e-6;

inline int sgn(reg double x){
	if(fabs(x)<eps)
		return 0;
	else
		return x<0?-1:1;
}

inline double sqr(reg double x){
	return x*x;
}

const int MAXN=2e3+5;

struct Vector{
	double x,y;
	inline Vector(reg double x=0,reg double y=0):x(x),y(y){
		return;
	}
	inline Vector operator+(const Vector& a)const{
		return Vector(x+a.x,y+a.y);
	}
	inline Vector operator-(const Vector& a)const{
		return Vector(x-a.x,y-a.y);
	}
	inline Vector operator*(const double a)const{
		return Vector(x*a,y*a);
	}
};

inline double dot(const Vector& a,const Vector& b){
	return a.x*b.x+a.y*b.y;
}

inline double cross(const Vector& a,const Vector& b){
	return a.x*b.y-a.y*b.x;
}

typedef Vector Point;

inline double getDis2(const Point& a,const Point& b){
	return dot(a-b,a-b);
}

inline double getDis(const Point& a,const Point& b){
	return sqrt(getDis2(a,b));
}

inline bool isEmpty(const Point& a){
	return a.x!=a.x||a.y!=a.y;
}

struct Circle{
	Point o;
	double r;
	inline bool contain(const Point& x)const{
		return sgn(sqr(r)-getDis2(x,o))>=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<i&&!isEmpty(lef);++j){
				Point tmp=getPot(a[i],a[j]);
				if(isEmpty(tmp)||tmp.x<=lef.x)
					lef=tmp;
			}
			for(reg int j=1;j<=i&&!isEmpty(lef);++j)
				if(!a[j].contain(lef))
					lef=Point(nan(""),nan(""));
		}
		if(isEmpty(lef)){
			printf("%d\n",i);
			return 0;
		}
	}
	puts("NIE");
	return 0;
}