用户工具

站点工具


2020-2021:teams:legal_string:王智彪:反演

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
2020-2021:teams:legal_string:王智彪:反演 [2021/08/04 21:07]
王智彪
2020-2021:teams:legal_string:王智彪:反演 [2021/08/06 18:33] (当前版本)
王智彪
行 9: 行 9:
 圆 $A$ 半径为 $r_{1}$ ,其反演图形的半径为 ${\frac 1 2}({\frac 1 {|OA|-r_{1}}}-{\frac 1 {|OA|+r_{1}}})R^{2}$ 。 圆 $A$ 半径为 $r_{1}$ ,其反演图形的半径为 ${\frac 1 2}({\frac 1 {|OA|-r_{1}}}-{\frac 1 {|OA|+r_{1}}})R^{2}$ 。
  
-===== 代码实现 =====+设 $O$ 点坐标为 $(x_{0},​y_{0})$ ,圆的圆心是 $A$ ,坐标为 $(x_{1},​y_{1})$ ,反演圆的圆心是 $B$ 。 
 + 
 +显然有  
 + 
 +$x_{2}=x_{0}+{\frac {|OB|} {|OA|}}(x_{1}-x_{0})$  
 + 
 +$y_{2}=y_{0}+{\frac {|OB|} {|OA|}}(y_{1}-y_{0})$  
 + 
 +又因为 $|OB|$ 刚才已经算出来了,所以可以得到反演点坐标 
 + 
 +过点 $O$ 的圆 $A$ ,其反演图形是不过点 $O$ 的直线。(因为另一个点在无穷远,所以圆无穷大,就变成直线了)然后求出圆心相对要反演的圆的对称点的反演点,然后连接反演点和 $O$ 做个垂线,就是反演的线了。 
 + 
 +两个图形相切,他们的反演图形也相切。 
 + 
 +===== 算法实现 =====
  
 <hidden 实现> <hidden 实现>
行 16: 行 30:
  
 struct Inversion { struct Inversion {
- Point o;//​反演中心  + Point o;//​反演中心 
- double r;//​反演半径 ​+ double r;//​反演半径
  Inversion() {}  Inversion() {}
  Inversion(Point _o,double _r) {  Inversion(Point _o,double _r) {
行 23: 行 37:
  r=_r;  r=_r;
  }  }
- //​点的反演 flag为0获取失败 1获取成功 ​+ //​点的反演 flag为0获取失败 1获取成功
  void getPointInv(Point a,Point &aa,int &flag) {  void getPointInv(Point a,Point &aa,int &flag) {
  if(a==o) {  if(a==o) {
行 35: 行 49:
  aa=o+ptmp;​  aa=o+ptmp;​
  flag=1;  flag=1;
- }  +
- //​圆的反演 flag为1变成圆 -1变成直线 ​+ //​圆的反演 flag为1变成圆 -1变成直线
  void getCircleInv(circle c,Line &​l,​circle &cc,int &flag) {  void getCircleInv(circle c,Line &​l,​circle &cc,int &flag) {
  if(c.relation(o)^1) {  if(c.relation(o)^1) {
行 56: 行 70:
  cc.r=pp1.distance(pp2)/​2;​  cc.r=pp1.distance(pp2)/​2;​
  return;  return;
-+ }
  flag=-1;  flag=-1;
  Point ptmp=c.p*2-o,​pptmp,​p1,​p2;​  Point ptmp=c.p*2-o,​pptmp,​p1,​p2;​
行 66: 行 80:
  l=Line(pptmp,​p1);​  l=Line(pptmp,​p1);​
  }  }
-}iv;+ //​直线的反演 成圆 
 + void getLineInv(Line L,circle &cc,int &flag) { 
 + if(L.relation(o)==3) { 
 + flag=0;​ 
 + return;​ 
 +
 + flag=1; 
 + Point p=L.lineprog(o),​ans;​ 
 + int f; 
 + getPointInv(p,​ans,​f);​ 
 + cc.r=ans.distance(o)/​2;​ 
 + cc.p=(ans+o)/​2;​ 
 +
 +} iv; 
  
 </​code>​ </​code>​
  
 </​hidden>​ </​hidden>​
 +
 +===== 代码练习 =====
 +
 +给定两个圆和圆外一点,求过这个点且与这两个圆都外切的圆,输出他们的圆心坐标和半径。
 +
 +显然这个圆如果被那个点反演是一条线,然后另外两个圆还是圆,所以变成求公切线的问题,最后再反演回去,最后要注意要外切,所以两个圆心要都和给定点在切线的同一侧才可以,判断一下就行了。
 +
 +<hidden 代码>
 +
 +<code cpp>
 +
 +
 +
 +// 如果 A B 两点在直线同侧 返回 true
 + bool theSameSideOfLine(Point A, Point B) {
 + return sgn((A-s)^(e-s)) * sgn((B-s)^(e-s)) > 0;
 + }
 +
 +
 +
 +// a[i] 和 b[i] 分别是第i条切线在圆A和圆B上的切点 f[i]为1内切 为2外切
 +int getTangents(circle A, Point* a, Point* b,int* f) {
 + circle BB=(*this);
 + circle B=BB;
 + int cnt = 0;
 + if (A.r < B.r) {
 + swap(A, B);
 + swap(a, b);
 + }
 + double d2 =(A.p.x - B.p.x) * (A.p.x - B.p.x) + (A.p.y - B.p.y) * (A.p.y - B.p.y);
 + double rdiff = A.r - B.r;
 + double rsum = A.r + B.r;
 + if (sgn(d2 - rdiff * rdiff) < 0) return 0;  // 内含
 +
 +double base = atan2(B.p.y - A.p.y, B.p.x - A.p.x);
 +Point pa=Point(A.r,​0);​
 +Point pb=Point(B.r,​0);​
 +Point p0=Point(0,​0);​
 +if (sgn(d2) == 0 && sgn(A.r - B.r) == 0) return -1;  // 无限多条切线
 +if (sgn(d2 - rdiff * rdiff) == 0) {  // 内切,一条切线
 + a[cnt] = A.p+pa.rotate(p0,​base);​
 + b[cnt] = B.p+pb.rotate(p0,​base);​
 + f[cnt] = 1;
 + ++cnt;
 + return 1;
 +}
 +// 有外公切线
 +double ang = acos(rdiff / sqrt(d2));
 +a[cnt] = A.p+pa.rotate(p0,​base+ang);​
 +b[cnt] = B.p+pb.rotate(p0,​base+ang);​
 +f[cnt] = 2;
 +++cnt;
 +a[cnt] = A.p+pa.rotate(p0,​base-ang);​
 +b[cnt] = B.p+pb.rotate(p0,​base-ang);​
 +f[cnt] = 2;
 +++cnt;
 +if (sgn(d2 - rsum * rsum) == 0) {  // 一条内公切线
 + a[cnt] = A.p+pa.rotate(p0,​base);​
 + b[cnt] = B.p+pb.rotate(p0,​base+pi);​
 + f[cnt] = 1;
 + ++cnt;
 +} else if (sgn(d2 - rsum * rsum) > 0) {  // 两条内公切线
 + double ang = acos(rsum / sqrt(d2));
 + a[cnt] = A.p+pa.rotate(p0,​base+ang);​
 + b[cnt] = B.p+pb.rotate(p0,​base+pi+ang);​
 + f[cnt] = 1;
 + ++cnt;
 + a[cnt] = A.p+pa.rotate(p0,​base-ang);​
 + b[cnt] = B.p+pb.rotate(p0,​base+pi-ang);​
 + f[cnt] = 1;
 + ++cnt;
 +}
 +return cnt;
 +
 +}  // 两圆公切线 返回切线的条数,-1表示无穷多条切线
 +
 +
 +
 +Point LA[1010], LB[1010];
 +circle ansc[1010];
 +int fl[1010];
 +int main() {
 + int t;
 + cin>>​t;​
 + circle c1,c2;
 + circle cc1,cc2;
 + Point pt;
 + while(t--) {
 + c1.p.input();​
 + scanf("​%lf",&​c1.r);​
 + c2.p.input();​
 + scanf("​%lf",&​c2.r);​
 + pt.input();​
 + iv=Inversion(pt,​10.0);​
 + Line lt;
 + int f;
 + iv.getCircleInv(c1,​lt,​cc1,​f);​
 + iv.getCircleInv(c2,​lt,​cc2,​f);​
 + Line l1,​l2,​l3,​l4;​
 + circle C1,​C2,​C3,​C4;​
 + int q = cc2.getTangents(cc1,​ LA, LB, fl), ans = 0;
 + for (int i = 0; i < q; ++i) {
 + Line lt=Line(LA[i],​LB[i]);​
 + if (lt.theSameSideOfLine(cc1.p,​ cc2.p)) {
 + if (!lt.theSameSideOfLine(pt,​ cc1.p)) continue;
 + iv.getLineInv(lt,​ansc[ans],​f);​
 + ans++;
 + }
 + }
 + printf("​%d\n",​ ans);
 + for (int i = 0; i < ans; ++i) {
 + printf("​%.8f %.8f %.8f\n",​ ansc[i].p.x,​ ansc[i].p.y,​ ansc[i].r);
 + }
 + }
 + return 0;
 +}
 +
 +</​code>​
 +
 +</​hidden>​
 +
 +
2020-2021/teams/legal_string/王智彪/反演.1628082473.txt.gz · 最后更改: 2021/08/04 21:07 由 王智彪