[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;
}