[CF gym 100962B]Black Sabbath(voronoi图)

题面

http://codeforces.com/gym/100962/attachments B题

题解

前置知识

本题给出n个矩形上两两相离的圆,要求使用m条线段将它们两两隔开。\(n{\leq}1000,m{\leq}10000\)

容易想到voronoi图,但是voronoi图解决的是点的问题,而本题要求的是圆。

考虑将voronoi图做一个拓展,从寻找两相邻点的中垂线,拓展为寻找两相邻圆的根轴。这样的好处在于它仍然保持了voronoi图原来的性质:因为原来是三个两两相邻的点,它们两两的中垂线共点(即外心);而现在是三个两两相邻的圆,它们两两的根轴共点(即根心)。所以这个拓展后的图仍然能够做到点数、边数均为\(O(n)\)

时间复杂度:voronoi图最快可以做到\(O(n \log n)\),详见《金牌之路——高中计算机竞赛解题指导》(这就是为什么题目末尾说此题n可以等于1e5)。但是由于太菜写不出来而且此题的n较小,所以就写了\(O(n^2 \log n)\)的暴力(就是对于每一个圆做一个半平面交)。

代码

#include<bits/stdc++.h>

using namespace std;

#define rg register
#define eps 1e-9
#define In inline
#define N 1000

In int read(){
	int s = 0,ww = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9'){if(ch == '-')ww = -1;ch = getchar();}
	while('0' <= ch && ch <= '9'){s = 10 * s + ch - '0';ch = getchar();}
	return s * ww;
}

In void write(int x){
	if(x < 0)putchar('-'),x = -x;
	if(x > 9)write(x / 10);
	putchar('0' + x % 10);
}

In int sgn(double x){
	if(x < -eps)return -1;
	return x > eps;
}

In double sqr(double x){return x * x;}

struct vec{
	double x,y;
	vec(){}
	vec(double _x,double _y){x = _x,y = _y;}
	In friend vec operator + (vec a,vec b){
		return vec(a.x + b.x,a.y + b.y);
	}
	In friend vec operator - (vec a,vec b){
		return vec(a.x - b.x,a.y - b.y);
	}
	In friend vec operator * (vec a,double k){
		return vec(a.x * k,a.y * k);
	}
	In friend vec operator / (vec a,double k){
		return vec(a.x / k,a.y / k);
	}
	In friend double Dot(vec a,vec b){
		return a.x * b.x + a.y * b.y;
	}
	In friend double Cross(vec a,vec b){
		return a.x * b.y - a.y * b.x;
	}
};

struct line{
	vec p,v;
	line(){}
	line(vec _p,vec _v){p = _p,v= _v;}
	In friend bool prl(line a,line b){
		return sgn(Cross(a.v,b.v)) == 0;
	}
	In friend bool samedir(line a,line b){
		return prl(a,b) && sgn(Dot(a.v,b.v)) == 1;
	}
	In friend vec Its(line a,line b){
		double x = Cross(b.v,a.p - b.p),y = Cross(a.v,b.v);
		return a.p + a.v * x / y;
	}
};

In vec DotOnLine(double A,double B,double C){
	if(sgn(A) != 0)return vec(-C / A,0);
	else return vec(0,-C / B);
}

In bool InUpper(vec a){
	return sgn(a.y) == 1 || (sgn(a.y)==0&&sgn(a.x)==-1);
}

In bool cmp(line a,line b){ 
	bool fa = InUpper(a.v),fb = InUpper(b.v);
	if(fa ^ fb)return fa < fb;
	else{
		int f = sgn(Cross(a.v,b.v));
		if(f != 0)return f == 1;
		else return sgn(Cross(b.p-a.p,a.v)) >= 0;
	}
}

In bool include(line a,vec p){
	return sgn(Cross(a.v,p-a.p)) == 1;
}

In bool check(line a,line b,line c){
	return include(c,Its(a,b));
}

int w,h,n;

void HalfPlaneIts(line *l,int n){
	l[++n] = line(vec(0,0),vec(1,0));
	l[++n] = line(vec(w,0),vec(0,1));
	l[++n] = line(vec(w,h),vec(-1,0));
	l[++n] = line(vec(0,h),vec(0,-1));
	sort(l + 1,l + n + 1,cmp);
	deque<line>q;
	q.clear();
	for(rg int i = 1;i <= n;i++){
		if(i > 1 && samedir(l[i],l[i-1]))continue;
		while(q.size() > 1 && !check(q[q.size()-2],q[q.size()-1],l[i]))q.pop_back();
		while(q.size() > 1 && !check(q[1],q[0],l[i]))q.pop_front();
		q.push_back(l[i]);
	}
	while(q.size() > 2 && !check(q[q.size()-2],q[q.size()-1],q[0]))q.pop_back();
	write(q.size()),putchar('\n');
	for(rg int i = 0;i < q.size() - 1;i++){
		vec p = Its(q[i],q[i+1]);
		printf("%.9lf %.9lf\n",p.x,p.y);
	}
	vec p = Its(q[q.size()-1],q[0]);
	printf("%.9lf %.9lf\n",p.x,p.y);
}

int x[N+5],y[N+5],r[N+5];
int cnt;
line l[N+5];

int main(){
	w = read(),h = read(),n = read();
	for(rg int i = 1;i <= n;i++)x[i] = read(),y[i] = read(),r[i] = read();
	for(rg int i = 1;i <= n;i++){
		cnt = 0;
		for(rg int j = 1;j <= n;j++){
			if(i == j)continue;
			double A = 2 * x[j] - 2 * x[i];
			double B = 2 * y[j] - 2 * y[i];
			double C = sqr(x[i]) - sqr(x[j]) + sqr(y[i]) - sqr(y[j]) - sqr(r[i]) + sqr(r[j]);
			l[++cnt] = line(DotOnLine(A,B,C),vec(y[i]-y[j],x[j]-x[i])); //根轴的方程就是两圆方程相减
		}
		HalfPlaneIts(l,cnt);
	}
	return 0;
}
posted @ 2020-02-19 15:31  coder66  阅读(190)  评论(0编辑  收藏  举报