Forgot password?
 Register account
View 147|Reply 0

[几何] Apollonius问题 一点两圆

[Copy link]

3158

Threads

7933

Posts

45

Reputation

Show all posts

hbghlyj posted 2023-4-16 01:04 |Read mode
「ICPC 2013 杭州赛区」Problem of Apollonius
题目大意
求过两圆外一点,且与两圆相切的所有的圆。

解法
首先考虑解析几何解法,似乎很难求解。
考虑以需要经过的点为反演中心进行反演(反演半径任意),所求的圆的反演图形是一条直线,且与题目给出两圆的反演图形相切。
于是题目经过反演变换后转变为:求两圆的所有公切线。

求出公切线后,反演回原平面即可。
  1. #include <algorithm>
  2. #include <cmath>
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <iostream>
  6. #include <vector>
  7. using namespace std;
  8. const double EPS = 1e-8;       // 精度系数
  9. const double PI = acos(-1.0);  // π
  10. const int N = 4;
  11. struct Point {
  12.   double x, y;
  13.   Point(double x = 0, double y = 0) : x(x), y(y) {}
  14.   const bool operator<(Point A) const { return x == A.x ? y < A.y : x < A.x; }
  15. };  // 点的定义
  16. typedef Point Vector;  // 向量的定义
  17. Vector operator+(Vector A, Vector B) {
  18.   return Vector(A.x + B.x, A.y + B.y);
  19. }  // 向量加法
  20. Vector operator-(Vector A, Vector B) {
  21.   return Vector(A.x - B.x, A.y - B.y);
  22. }  // 向量减法
  23. Vector operator*(Vector A, double p) {
  24.   return Vector(A.x * p, A.y * p);
  25. }  // 向量数乘
  26. Vector operator/(Vector A, double p) {
  27.   return Vector(A.x / p, A.y / p);
  28. }  // 向量数除
  29. int dcmp(double x) {
  30.   if (fabs(x) < EPS)
  31.     return 0;
  32.   else
  33.     return x < 0 ? -1 : 1;
  34. }  // 与0的关系
  35. double Dot(Vector A, Vector B) { return A.x * B.x + A.y * B.y; }  // 向量点乘
  36. double Length(Vector A) { return sqrt(Dot(A, A)); }  // 向量长度
  37. double Cross(Vector A, Vector B) { return A.x * B.y - A.y * B.x; }  // 向量叉乘
  38. Point GetLineProjection(Point P, Point A, Point B) {
  39.   Vector v = B - A;
  40.   return A + v * (Dot(v, P - A) / Dot(v, v));
  41. }  // 点在直线上投影
  42. struct Circle {
  43.   Point c;
  44.   double r;
  45.   Circle() : c(Point(0, 0)), r(0) {}
  46.   Circle(Point c, double r = 0) : c(c), r(r) {}
  47.   Point point(double a) {
  48.     return Point(c.x + cos(a) * r, c.y + sin(a) * r);
  49.   }  // 输入极角返回点坐标
  50. };   // 圆
  51. // a[i] 和 b[i] 分别是第i条切线在圆A和圆B上的切点
  52. int getTangents(Circle A, Circle B, Point* a, Point* b) {
  53.   int cnt = 0;
  54.   if (A.r < B.r) {
  55.     swap(A, B);
  56.     swap(a, b);
  57.   }
  58.   double d2 =
  59.       (A.c.x - B.c.x) * (A.c.x - B.c.x) + (A.c.y - B.c.y) * (A.c.y - B.c.y);
  60.   double rdiff = A.r - B.r;
  61.   double rsum = A.r + B.r;
  62.   if (dcmp(d2 - rdiff * rdiff) < 0) return 0;  // 内含
  63.   double base = atan2(B.c.y - A.c.y, B.c.x - A.c.x);
  64.   if (dcmp(d2) == 0 && dcmp(A.r - B.r) == 0) return -1;  // 无限多条切线
  65.   if (dcmp(d2 - rdiff * rdiff) == 0) {  // 内切,一条切线
  66.     a[cnt] = A.point(base);
  67.     b[cnt] = B.point(base);
  68.     ++cnt;
  69.     return 1;
  70.   }
  71.   // 有外公切线
  72.   double ang = acos(rdiff / sqrt(d2));
  73.   a[cnt] = A.point(base + ang);
  74.   b[cnt] = B.point(base + ang);
  75.   ++cnt;
  76.   a[cnt] = A.point(base - ang);
  77.   b[cnt] = B.point(base - ang);
  78.   ++cnt;
  79.   if (dcmp(d2 - rsum * rsum) == 0) {  // 一条内公切线
  80.     a[cnt] = A.point(base);
  81.     b[cnt] = B.point(PI + base);
  82.     ++cnt;
  83.   } else if (dcmp(d2 - rsum * rsum) > 0) {  // 两条内公切线
  84.     double ang = acos(rsum / sqrt(d2));
  85.     a[cnt] = A.point(base + ang);
  86.     b[cnt] = B.point(PI + base + ang);
  87.     ++cnt;
  88.     a[cnt] = A.point(base - ang);
  89.     b[cnt] = B.point(PI + base - ang);
  90.     ++cnt;
  91.   }
  92.   return cnt;
  93. }  // 两圆公切线 返回切线的条数,-1表示无穷多条切线
  94. Circle Inversion_C2C(Point O, double R, Circle A) {
  95.   double OA = Length(A.c - O);
  96.   double RB = 0.5 * ((1 / (OA - A.r)) - (1 / (OA + A.r))) * R * R;
  97.   double OB = OA * RB / A.r;
  98.   double Bx = O.x + (A.c.x - O.x) * OB / OA;
  99.   double By = O.y + (A.c.y - O.y) * OB / OA;
  100.   return Circle(Point(Bx, By), RB);
  101. }  // 点 O 在圆 A 外,求圆 A 的反演圆 B,R 是反演半径
  102. Circle Inversion_L2C(Point O, double R, Point A, Vector v) {
  103.   Point P = GetLineProjection(O, A, A + v);
  104.   double d = Length(O - P);
  105.   double RB = R * R / (2 * d);
  106.   Vector VB = (P - O) / d * RB;
  107.   return Circle(O + VB, RB);
  108. }  // 直线反演为过 O 点的圆 B,R 是反演半径
  109. bool theSameSideOfLine(Point A, Point B, Point S, Vector v) {
  110.   return dcmp(Cross(A - S, v)) * dcmp(Cross(B - S, v)) > 0;
  111. }  // 返回 true 如果 A B 两点在直线同侧
  112. int main() {
  113.   int T;
  114.   scanf("%d", &T);
  115.   while (T--) {
  116.     Circle A, B;
  117.     Point P;
  118.     scanf("%lf%lf%lf", &A.c.x, &A.c.y, &A.r);
  119.     scanf("%lf%lf%lf", &B.c.x, &B.c.y, &B.r);
  120.     scanf("%lf%lf", &P.x, &P.y);
  121.     Circle NA = Inversion_C2C(P, 10, A);
  122.     Circle NB = Inversion_C2C(P, 10, B);
  123.     Point LA[N], LB[N];
  124.     Circle ansC[N];
  125.     int q = getTangents(NA, NB, LA, LB), ans = 0;
  126.     for (int i = 0; i < q; ++i)
  127.       if (theSameSideOfLine(NA.c, NB.c, LA[i], LB[i] - LA[i])) {
  128.         if (!theSameSideOfLine(P, NA.c, LA[i], LB[i] - LA[i])) continue;
  129.         ansC[ans++] = Inversion_L2C(P, 10, LA[i], LB[i] - LA[i]);
  130.       }
  131.     printf("%d\n", ans);
  132.     for (int i = 0; i < ans; ++i) {
  133.       printf("%.8f %.8f %.8f\n", ansC[i].c.x, ansC[i].c.y, ansC[i].r);
  134.     }
  135.   }
  136.   return 0;
  137. }
Copy the Code
练习
「ICPC 2017 南宁赛区网络赛」Finding the Radius for an Inserted Circle
「CCPC 2017 网络赛」The Designer

Quick Reply

Advanced Mode
B Color Image Link Quote Code Smilies
You have to log in before you can reply Login | 快速注册

$\LaTeX$ formula tutorial

Mobile version

2025-6-8 06:54 GMT+8

Powered by Discuz!

Processed in 0.013413 second(s), 21 queries

× Quick Reply To Top Edit