Gym 100345J Zen Garden(点与圆公切线问题)

题意:一个x*y的矩形里,给了n个圆,n不超过10,保证圆与圆只有相离或外切两种关系,要求选择一个点,这个点的任意一条射线,都最多只与一个圆相交。射线与圆相交指的是射线与圆有两个交点。
链接:
http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=162109 (VJ)
http://codeforces.com/gym/100345/ (CF J题)

解法:圆不多,暴力可解。枚举两两圆的公切线,打入一个集合L里,再打入矩形的四条边。枚举L里两两条线,求交点,打入一个集合P里,再将矩形四个点和每个切点打入。可以认为答案一定在集合P里,否则就无解。枚举集合P里每个点,再枚举每个圆,这个点和圆做三条线,即两条公切线和一条圆心到该点的连线,这三条线都当作射线对待,然后再枚举除了当前圆以外的所有圆,如果存在射线和圆相交(交点数>2),则当前点不能作为答案,否则可以。

小结:思维上不是太难,编码难度有点大,这里涉及到圆和圆求公切线,直线和圆求公切线等问题,都需要分情况讨论,写起来不那么简单,建议形成模板块。

本题代码

//Hello. I'm Peter.
//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<iostream>
#include<sstream>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<functional>
#include<cctype>
#include<ctime>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
#define peter cout<<"i am peter"<<endl
#define fuck(x) cerr << #x << " <- " << x << endl
typedef long long ll;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
/*--------------------------定量和定函数--------------------------*/
const double eps = 1e-9, pi = acos(-1.0);
inline const int sgn(double x){
    if(fabs(x) < eps) return 0;
    else return x > 0? 1 : -1;
}
inline const double sq(double x){return x*x;}


/*--------------------------向量--------------------------*/
struct Point{
    double x, y;
    Point(){};
    Point(double x1, double y1){x = x1, y = y1;}
};
typedef Point Vector;
Vector operator + (const Vector a, const Vector b){
    return Vector(a.x + b.x, a.y + b.y);
}
Vector operator - (const Vector a, const Vector b){
    return Vector(a.x - b.x, a.y - b.y);
}
double operator * (const Vector a, const Vector b){
    return a.x * b.x + a.y * b.y;
}
double operator % (const Vector a, const Vector b){
    return a.x * b.y - a.y * b.x;
}
Vector operator * (const Vector a, const double b){
    return Vector(a.x * b, a.y * b);
}
Vector operator * (const double b, const Vector a){
    return Vector(a.x * b, a.y * b);
}
Vector operator / (const Vector a, const double b){
    return Vector(a.x / b, a.y / b);
}
bool operator == (const Point a, const Point b){
    return sgn(a.x - b.x)==0 && sgn(a.y - b.y)==0;
}
bool operator || (const Vector a, const Vector b){
    return sgn(a % b)==0;
}
bool operator / (const Vector a, const Vector b){
    return sgn(a % b)!=0;
}
double Length(Vector v){
    return (double)sqrt((double)(v.x * v.x + v.y * v.y));
}
double LenSq(Vector v){
    return v.x*v.x + v.y*v.y;
}
double Dis(Point a, Point b){
    return Length(a - b);
}
Vector Rotate(Vector v, double rad){
    return Vector(v.x * cos(rad) - v.y * sin(rad), v.x * sin(rad) + v.y * cos(rad));
}
Vector Norv(Vector v){
    return Vector(v.y, -v.x);
}
Vector Unitv(Vector v){
    return v / Length(v);
}
double angle(Vector v){
    return atan2(v.y, v.x);
}
double angle(Vector a, Vector b){
    double ans = angle(a) - angle(b);
    while(sgn(ans) < 0) ans += 2*pi; while(sgn(ans) >= 2*pi) ans -= 2*pi;
    return fmin(ans, 2*pi - ans);
}

//以下是排序和去重使用的比较函数
bool cmpxy(const Point a, const Point b){
    if(sgn(a.x-b.x)) return a.x < b.x;
    else return a.y < b.y;
}
bool cmpeq(const Point a, const Point b){
    return a==b;
}
//以上是排序和去重使用的比较函数


/*--------------------------直线--------------------------*/
struct Line{
    Point p; Vector v;
    Line(){};
    Line(Point p1, Vector v1){p = p1, v = v1;}
};
Point operator / (const Line a, const Line b){
    double t = ((b.p - a.p) % b.v) / (a.v % b.v);
    return a.p + a.v * t;
}
double Dis(Point p, Line l){
    return fabs(l.v % (p - l.p)) / Length(l.v);
}


/*--------------------------射线--------------------------*/
typedef Line Ray;


/*--------------------------圆--------------------------*/
struct Circle{
    Point p;
    double r;
    Circle(){};
    Circle(Point p1, double r1){p = p1, r = r1;}
    Point point(double rad){
        return Point(p.x + r * cos(rad), p.y + r * sin(rad));
    }
};
//以下几个位置关系函数,有些题目重新书写会更好
bool ExTan (const Circle c1, const Circle c2){//外切
    return sgn(Dis(c1.p, c2.p) - (c1.r + c2.r)) == 0;
}
//以上几个位置关系函数,有些题目重新书写会更好

bool PointOnCircle(Point p, Circle c){
    return sgn(Dis(p, c.p) - c.r) == 0;
}
bool PointInCircle(Point p, Circle c){
    return sgn(Dis(p, c.p) - c.r) < 0;
}
void Tan_Circles_sub1(Circle c1, Circle c2, Line *l, int &nl){
    //第一种情况,求两圆两条外切线,前提是两圆相离或外切或相交
    //修改..
    if(c1.r > c2.r) swap(c1, c2);//保证c1较小
    Vector v12 = c2.p - c1.p;
    double av = angle(v12);
    double b = c2.r - c1.r;
    double c = Dis(c1.p, c2.p);
    double a = acos(b/c);
    Point x11, x12, x21, x22;
    x11 = c1.point(av + a), x12 = c1.point(av - a);
    x21 = c2.point(av + a), x22 = c2.point(av - a);

    l[nl++] = Line(x11, x21 - x11), l[nl++] = Line(x12, x22 - x12);
}
void Tan_Circles_sub2(Circle c1, Circle c2, Line *l, int &nl){
    //第二种情况,求两圆两条内切线,前提是两圆相离
    //修改..
    double dis = Dis(c1.p, c2.p);
    double d2 = dis / ((c1.r/c2.r) + 1);
    double d1 = dis - d2;

    double a1 = acos(c1.r / d1);
    Vector v12 = c2.p - c1.p;
    double av12 = angle(v12);
    Point x11, x12;
    x11 = c1.point(av12 + a1), x12 = c1.point(av12 - a1);

    double a2 = acos(c2.r / d2);
    Vector v21 = c1.p - c2.p;
    double av21 = angle(v21);
    Point x21, x22;
    x21 = c2.point(av21 + a2), x22 = c2.point(av21 - a2);

    l[nl++] = Line(x11, x21 - x11), l[nl++] = Line(x12, x22 - x12);
}
void Tan_Circles_sub3(Circle c1, Circle c2, Line *l, int &nl){
    //第三种情况,两圆外切时,求一条内切线
    if(!ExTan(c1, c2)) return;

    Vector v12 = c2.p - c1.p;
    Point x = c1.p + (Unitv(v12)*(c1.r));
    Vector norv = Norv(v12);

    l[nl++] = Line(x, norv);
}
void Tan_Circles(Circle c1, Circle c2, Line *l, int &nl){
    //修改
    //nl = 0;//这里经常需要修改,特别注意
    Tan_Circles_sub1(c1, c2, l, nl);
    Tan_Circles_sub2(c1, c2, l, nl);
    Tan_Circles_sub3(c1, c2, l, nl);
    //修改
}


/*--------------------------不同类相交--------------------------*/
bool LineInterCircle(Line l, Circle c){
    return sgn(Dis(c.p, l) - c.r) < 0;
}
bool RayInterCircle(Line l, Circle c){
    return LineInterCircle(l, c) && sgn((c.p - l.p) * l.v) >0;
}
void Tan_PointCircle(Point p, Circle c, Line *l, int &nl){
    //求一个点到一个圆的两条切线,前提是该点不能在圆内

    if(PointInCircle(p, c)) return;

    nl = 0;//这里经常需要修改,特别注意
    if(PointOnCircle(p, c)){
        Vector v = Norv(p - c.p);
        l[nl++] = Line(p, v);
        //当求的是射线时,这里需要修改,特别注意
        //修改
        l[nl++] = Line(p, v * (-1));
        l[nl++] = Line(p, c.p - p);
        return;
    }
    else{
        double a = asin(c.r / Dis(p, c.p));
        Vector v;

        v = Rotate(c.p - p, a);
        l[nl++] = Line(p, v);

        v = Rotate(c.p - p, -a);
        l[nl++] = Line(p, v);

        //修改
        l[nl++] = Line(p, c.p - p);
    }
}


/*--------------------------读入--------------------------*/
Point readPoi(){
    int x, y;
    x = read(), y = read();
    //确保读入为int整形
    return Point(x, y);
}
Circle readCir(){
    Point p = readPoi();
    int r; r = read();
    //确保读入为int整形
    return Circle(p, r);
}
/*----------------------------------------------------*/

#define N 100010
int n,xx,yy;
Point p[N];
Line l[N];
Circle c[N];
int nump, numl;
bool inrange(double x, double a, double b){
    return sgn(a-x)<=0 && sgn(x-b)<=0;
}
bool inMaze(Point p){
    return inrange(p.x, 0, xx) && inrange(p.y, 0, yy);
}

int main(){
    freopen("zen.in","r",stdin);
    freopen("zen.out","w",stdout);

    cin>>n>>xx>>yy;
    for(int i = 0; i < n; i++) c[i] = readCir();
    nump = numl = 0;
    p[nump++] = Point(0, 0), p[nump++] = Point(xx, 0),  p[nump++] = Point(xx, yy), p[nump++] = Point(0, yy);
    for(int i = 0; i < nump; i++){
        int nex = i == nump-1? 0: i +1;
        l[numl++] = Line(p[i], p[nex] - p[i]);
    }

    for(int i = 0; i < n; i++){
        for(int j = i + 1; j < n; j++){
            int lastn;

            lastn = numl;
            Tan_Circles(c[i], c[j], l, numl);
            for(int k = lastn; k < numl; k++){
                p[nump++] = l[k].p;
            }

            lastn = numl;
            Tan_Circles(c[j], c[i], l, numl);
            for(int k = lastn; k < numl; k++){
                p[nump++] = l[k].p;
            }
            numl = lastn;
        }
    }

    for(int i = 0; i < numl; i++){
        for(int j = i + 1; j < numl; j++){
            if(l[i].v / l[j].v) p[nump++] = l[i] / l[j];
        }
    }

    sort(p, p + nump, cmpxy);
    nump = (int)(unique(p, p + nump, cmpeq) - p);

    for(int i = 0; i < nump; i++){
        if(!inMaze(p[i])) continue;

        bool ok = true;
        for(int j = 0; j < n && ok; j++){
            if(PointInCircle(p[i], c[j])) ok = false;
        }

        for(int j = 0; j < n && ok; j++){
            Line l1[10]; int numl1;
            Tan_PointCircle(p[i], c[j], l1, numl1);

            for(int a = 0; a < numl1 && ok; a++){
                for(int k = 0; k < n && ok; k++){
                    if(j == k) continue;

                    if(RayInterCircle(l1[a], c[k])) ok = false;
                }
            }
        }

        if(ok){
            printf("%.10f %.10f\n", p[i].x, p[i].y);
            return 0;
        }
    }
    printf("No Zen\n");

    return 0;
}

你可能感兴趣的:(点与圆公切线)