Hdu--4773,13杭州D(几何,圆的反演)
2015-02-24 16:06:13
这道题乍一看能直接数学搞... (就交给MO神犇吧...)
从一维反演推广而来的圆反演有蛮多性质,这题我们要关注:
(1)过反演中心的圆,反形为不过反演中心的直线。
(2)不过反演中心的直线,反形为过反演中心的圆。
例1 例2
(图中(0,0)为反演中心,蓝色圆为反演圆,绿色直线和棕色圆互为反形)
(3)不过反演中心的圆,反形也为不过反演中心的圆。
(图中绿色圆与黄色圆互为反形)
(4)反演不改变相切性。(定理:相切两圆的反象仍相切,若切点恰是反演中心,则其反象为两平行线)
其实(1)与(2)互逆,(4)也可证。
思路:首先我们把给定的P点看做反演中心,然后自定义反演半径的大小(为保证精度,不宜过小),这样我们就得到了反演圆 c(P,R)
接着,求出题目给出的两个圆c1,c2各自的反形c1’,c2’。
然后,作出c1’和c2’的外公切线,再将公切线反演回去就得到答案所需的圆了。
(依据:根据(4)的定理,我们倒着考虑,最终的图形为3个圆,答案圆与另外两圆外切,如果将这三个圆反演回去,答案圆因为过反演中心,所以反形是一条直线,
另外两个题目给出的圆因为没有过反演中心,所以反形是两个圆,由于相切性不变,这条直线与这两个反形圆均相切,即为公切线。)
注意:不仅要求的是公切线,为了防止出现内切的情况,我们要求的是“外公切线”,并且要使得P点和反形圆的圆心在外公切线的同一侧。
这个当时很不理解... 所以画了个图,P(0,0),两个大圆相距很近(这里暂时让他们相交,只为说明问题),他们的反形为两个很近的小圆,两条外公切线如图。
(1)对于红外公切线y=1/6,P点和小圆圆心在公切线异侧,公切线的反形(x^2+(y-3)^2=9)会内切大圆。
(2)对于绿外公切线y=1/2,P点和小圆圆心在公切线同侧,公切线的反形(x^2+(y-1)^2=1)会外切大圆。
由此,当P点和反形圆的圆心在外公切线的同一侧时,该外公切线才是符合要求的。
最后,将公切线反演回去就比较方便了,求出P点到直线距离,然后就可就出答案圆的半径,至于答案圆的圆心可以用向量相似解决。
(A了好久... 如有错误,还望指出)
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <vector> 6 #include <map> 7 #include <set> 8 #include <stack> 9 #include <queue> 10 #include <string> 11 #include <iostream> 12 #include <algorithm> 13 using namespace std; 14 15 #define MEM(a,b) memset(a,b,sizeof(a)) 16 #define REP(i,n) for(int i=1;i<=(n);++i) 17 #define REV(i,n) for(int i=(n);i>=1;--i) 18 #define FOR(i,a,b) for(int i=(a);i<=(b);++i) 19 #define RFOR(i,a,b) for(int i=(a);i>=(b);--i) 20 #define getmid(l,r) ((l) + ((r) - (l)) / 2) 21 #define MP(a,b) make_pair(a,b) 22 23 typedef long long ll; 24 typedef pair<int,int> pii; 25 const int INF = (1 << 30) - 1; 26 const double eps = 1e-10; 27 28 double add(double a,double b){ //考虑误差的浮点加法 29 if(abs(a + b) < eps * (abs(a) + abs(b))) return 0; 30 return a + b; 31 } 32 33 struct Point{ 34 double x,y; 35 Point(double tx = 0,double ty = 0) : x(tx),y(ty) {} 36 Point operator + (Point p){ 37 return Point(add(x,p.x),add(y,p.y)); 38 } 39 Point operator - (Point p){ 40 return Point(add(x,-p.x),add(y,-p.y)); 41 } 42 Point operator * (double d){ 43 return Point(x * d,y * d); 44 } 45 Point operator / (double d){ 46 return Point(x / d,y / d); 47 } 48 Point Move(double a,double d){ 49 return Point(x + d * cos(a),y + d * sin(a)); 50 } 51 void Read(){ 52 scanf("%lf%lf",&x,&y); 53 } 54 }; 55 56 struct Circle{ 57 Point o; 58 double r; 59 Circle(double tx = 0,double ty = 0,double tr = 0) : o(tx,ty),r(tr) {} 60 void Read(){ 61 o.Read(); 62 scanf("%lf",&r); 63 } 64 void Out(){ 65 printf("%.8f %.8f %.8f\n",o.x,o.y,r); 66 } 67 }; 68 69 int Sign(double x){ //判断x的正负 70 return (x > eps) - (x < -eps); 71 } 72 73 double Cross(Point a,Point b,Point c){ //叉积 74 return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y); 75 } 76 77 double Dis(Point a,Point b){ 78 return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); 79 } 80 81 Circle c[5]; 82 Point P; 83 int T,tot; 84 double R; 85 86 Circle Inver(Circle c1){ //反演过程 87 Circle res; 88 double oc1 = Dis(P,c1.o); 89 double k1 = 1.0 / (oc1 - c1.r); 90 double k2 = 1.0 / (oc1 + c1.r); 91 res.r = 0.5 * (k1 - k2) * R * R; 92 double oc2 = 0.5 * (k1 + k2) * R * R; 93 res.o = P + (c1.o - P) * oc2 / oc1; 94 return res; 95 } 96 97 void Mark(Point a,Point b){ //记下答案圆 98 ++tot; 99 double t = fabs(Cross(a,P,b) / Dis(a,b)); //求出P点到直线的距离 100 c[tot].r = R * R / (2.0 * t); 101 double d = Dis(a,c[1].o); 102 c[tot].o = P + (a - c[1].o) * (c[tot].r / d); //因为向量(a,c[1].o)与公切线垂直,所以可以利用其长度相似出P到c[tot]圆心的距离 103 } 104 105 void Solve(){ 106 REP(i,2) c[i] = Inver(c[i]); //将已知两圆反演 107 if(c[1].r < c[2].r) swap(c[1],c[2]); //c[1]圆的半径较大 108 Point tmp = c[2].o - c[1].o; 109 double a1 = atan2(tmp.y,tmp.x); //atan2的经典应用,求出直线的倾斜角! 110 double a2 = acos((c[1].r - c[2].r) / Dis(c[1].o,c[2].o)); 111 Point P1 = c[1].o.Move(a1 + a2,c[1].r); 112 Point P2 = c[2].o.Move(a1 + a2,c[2].r); 113 if(Sign(Cross(P1,c[1].o,P2)) == Sign(Cross(P1,P,P2))) Mark(P1,P2); //保证P与c[1]圆心在公切线同侧 114 P1 = c[1].o.Move(a1 - a2,c[1].r); //同样要考虑公切线在下(上)方的情况 115 P2 = c[2].o.Move(a1 - a2,c[2].r); 116 if(Sign(Cross(P1,c[1].o,P2)) == Sign(Cross(P1,P,P2))) Mark(P1,P2); 117 } 118 119 int main(){ 120 R = 10.0; //自定义的反演半径 121 scanf("%d",&T); 122 REP(tt,T){ 123 tot = 2; 124 REP(i,2) c[i].Read(); 125 P.Read(); 126 Solve(); 127 printf("%d\n",tot - 2); 128 FOR(i,3,tot) c[i].Out(); 129 } 130 return 0; 131 }