设O为反演中心。
1.圆O外的点反演之后在园内,圆O内的点反演之后在园外,圆O上的点反演之后仍在圆上。
2.不过点O的圆A,其反演图形,也是不过点O的圆。
3.过点O的圆,其反演图形是一个不过点O的直线。
4.若两图形相切,则他们的反演图形也相切。
5.两个不经过反演点的外切的圆,反演之后的图形为相交的两条直线,如果其中一个圆经过反演点,那么反演之后的图形为一个圆和它的一条切线并且反演点和反演后的圆的圆心在切线的同一侧,内切的话反演中心和反演圆的圆心在异侧。
给定你两个圆,然后让你找出跟这两个圆外切的圆。直接找外切的圆会十分的麻烦,这里就考虑反演的性质,如果我们把给定的这个圆反演之后,然后再求出反演后的这两个圆的公切线,再将得到的公切线反演回去即可得到我们想要的外公切圆。(PS:这里还需要几个特判利用上面的那个性质五,内公切线不找)
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define inf 0x3f3f3f3f const double eps = 1e-9; int sign(double x)//判断符号可用 { if(fabs(x) <= eps) return 0; if(x > 0) return 1; else return -1; } struct Point { double x,y; Point(){} //定义运算 Point(double _x,double _y){x = _x;y = _y;} Point operator + (const Point &b)const{ return Point(x+b.x,y+b.y); } Point operator - (const Point &b)const{ return Point(x-b.x,y-b.y); } Point operator * (const double &k)const{//乘常数 return Point(x*k,y*k); } Point operator / (const double &k)const{ return Point(x/k,y/k); } //点的叉积和点积都是数 //点积 double operator * (const Point &b)const{ return x*b.x+y*b.y; } //叉积 double operator ^ (const Point &b)const{ return x*b.y-y*b.x; } Point rev(double rad,double r){// rad代表旋转的弧度 r代表圆的半径 return Point(x+r*cos(rad),y+r*sin(rad)); }//求圆上任意一角度的点的坐标 double powlen(){return x*x+y*y;}; double len(){return sqrt(powlen());} }; double cross(Point a,Point b){//叉积 return a.x*b.y-a.y*b.x; } double dot(Point a,Point b){//点积 return a.x*b.x+a.y*b.y; } double dis(Point a,Point b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } struct Circle { Point o; double r; Circle(){} Circle(Point o,double r):o(o),r(r){} }; Circle c[7];//1和2 代表题目给出的那两个圆 同时又是他们各自的反演 Point P;//反演点 int tot;//答案圆个数 double R;//反演半径 Circle circle_inversion(Circle c1){//圆反演后的圆的半径好圆心的计算公式 Circle res; double oc1 = dis(P,c1.o); double k1 = 1.0/(oc1 - c1.r); double k2 = 1.0/(oc1 + c1.r); res.r = 0.5 * (k1 - k2) * R * R;//反演图形的半径 double oc2 = 0.5 * (k1 + k2) * R * R; res.o = P + (c1.o - P) * oc2 / oc1;//反演图形的圆心 return res; } void line_inversion(Point a,Point b){//得到的切线反演回答案圆 ++tot; double t = fabs(cross(P-a,b-a)/dis(a,b));//求出反演中心到直线的距离 c[tot].r = R * R / (2.0 * t);//推推公式就有了 double d = dis(a,c[1].o); c[tot].o = P + (a-c[1].o) * (c[tot].r / d);//利用相似求出圆的圆心 } //这个题目里只需要 求出两个圆的外公切线即可 //因为内公切线反演回去的圆会有一个外切一个内切 不符合题意 void solve(){ for(int i = 1;i <= 2;i ++) c[i] = circle_inversion(c[i]); if(c[1].r < c[2].r) swap(c[1],c[2]);//令c1为半径较大的那个圆 Point tmp = c[2].o - c[1].o; double base = atan2(tmp.y,tmp.x);//求出两圆心向量对应的极角 double ang = acos((c[1].r - c[2].r) / dis(c[1].o,c[2].o));//求出对应的圆心角的度数 Point p1 = c[1].o.rev(base + ang,c[1].r);//求出第一条外切线的切点 Point p2 = c[2].o.rev(base + ang,c[2].r); if(sign(cross(P-p1,p2-p1)) == sign(cross(c[1].o-p1,p2-p1)))//检验求出切点的合法性 即应该在同侧 对应反演性质5 line_inversion(p1,p2); p1 = c[1].o.rev(base - ang,c[1].r); p2 = c[2].o.rev(base - ang,c[2].r); if(sign(cross(P-p1,p2-p1)) == sign(cross(c[1].o-p1,p2-p1)))//合法性 line_inversion(p1,p2); } int main() { R = 10.0;//自定义的反演半径的大小 int t; scanf("%d",&t); while(t--){ tot = 2; for(int i = 1;i <= 2;i ++) scanf("%lf%lf%lf",&c[i].o.x,&c[i].o.y,&c[i].r); scanf("%lf%lf",&P.x,&P.y); solve(); printf("%d\n",tot-2); for(int i = 3;i <= tot;i ++) printf("%.8f %.8f %.8f\n",c[i].o.x,c[i].o.y,c[i].r); } return 0; }