1. 程式人生 > >UOJ #277 BZOJ 4739 定向越野 (計算幾何、最短路)

UOJ #277 BZOJ 4739 定向越野 (計算幾何、最短路)

題目連結:
https://www.lydsy.com/JudgeOnline/problem.php?id=4739
http://uoj.ac/problem/277

不難得出一個結論: 兩圓之間最短路一定沿著圓的公切線走。然後得到如下演算法:每兩個圓之間作公切線(4條),如果一條公切線不穿過其他圓,就把這兩個點(圖論中的點)之間連上邊,邊權為切線長;同一圓上相鄰兩點連邊,邊權為圓上距離,然後S,T分別向每個圓作切線如果不和其他圓相交就連邊。這樣的話點數、邊數是 O (

n 2 ) O(n^2) 級別的,使用Dijkstra演算法求最短路即可。時間複雜度瓶頸在於對於一條邊列舉每一個圓去check是否相交,總時間複雜度 O (
n 3 ) O(n^3)
. 奇蹟般地能跑過。
下面重點講解一下我是如何求兩圓公切線以及點和圓的切線的:
先來說兩圓公切線:因為兩圓外離所以一定有 4 4
條公切線, 2 2 2 2 外。
考慮先把圓按半徑從大到小排序,現在要從半徑大的圓向半徑小的圓作 4 4 條切線。
設兩圓圓心(已知)分別為 O 1 , O 2 O_1, O_2 , 圓心的直線距離 O 1 O 2 = d O_1O_2=d , 半徑分別為 r 1 r 2 r_1r_2 , 一條外公切線為 A B AB (未知)。
O 2 O_2 O 2 E A O 1 O_2E\perp AO_1 D D , 則 A E O 2 B AEO_2B 為矩形。 A E = B O 2 = r 2 AE=BO_2=r_2 , E O 1 = A O 1 A E = r 1 r 2 EO_1=AO_1-AE=r_1-r_2 , 又 O 1 E O 2 E O_1E\perp O_2E , 可用勾股定理算出切線長 A B = E O 2 = d 2 ( r 1 r 2 ) 2 AB=EO_2=\sqrt{d^2-(r_1-r_2)^2} . 同時有 cos E O 1 O 2 = r 1 r 2 d \cos \angle EO_1O_2=\frac{r_1-r_2}{d} , 可利用acos函式算出 E O 1 O 2 \angle EO_1O_2 的值。然後求出向量 O A = r 1 d v \vec {OA}=\frac{r_1}{d}\vec v 即可,其中 v \vec v O 1 O 2 \vec {O_1O_2} 逆時針旋轉 E O 1 O 2 \angle EO_1O_2 的向量。
另外一條外公切線同理,把旋轉度數取反即可。
程式碼如下:

   Line tmp; double d = EuclidDist(a[i].o,a[j].o); Vector v; double ang; Point p1,p2;
   //Out 1
   ang = acos((a[i].r-a[j].r)/d); v = rotate(Vector(a[j].o-a[i].o),ang)/d;
   p1 = a[i].o+v*a[i].r,p2 = a[j].o+v*a[j].r;
   tmp = Line(p1,p2);
   //Out 2
   v = rotate(Vector(a[j].o-a[i].o),-ang)/d;
   p1 = a[i].o+v*a[i].r,p2 = a[j].o+v*a[j].r;
   tmp = Line(p1,p2);

對於兩條內公切線,只要 E O 2 = d 2 ( r 1 + r 2 ) 2 EO_2=\sqrt{d^2-(r_1+r_2)^2} 然後計算即可。注意內公切線的向量是一加一減。

   //In 1
   ang = acos((a[i].r+a[j].r)/d); v = rotate(Vector(a[j].o-a[i].o),ang)/d;
   p1 = a[i].o+v*a[i].r,p2 = a[j].o-v*a[j].r;
   tmp = Line(p1,p2);
   //In 2
   v = rotate(Vector(a[j].o-a[i].o),-ang)/d;
   p1 = a[i].o+v*a[i].r,p2 = a[j].o-v*a[j].r;
   tmp = Line(p1,p2);

然後再來看過一點作圓的兩條切線:
和剛才的做法類似,逆時針旋轉向量 O P \vec {OP} arcsin r d \arcsin \frac{r}{d} 角即可。然後再除以 d d 乘以 d 2 r 2 \sqrt{d^2-r^2}