UOJ #277 BZOJ 4739 定向越野 (計算幾何、最短路)
題目連結:
https://www.lydsy.com/JudgeOnline/problem.php?id=4739
http://uoj.ac/problem/277
不難得出一個結論: 兩圓之間最短路一定沿著圓的公切線走。然後得到如下演算法:每兩個圓之間作公切線(4條),如果一條公切線不穿過其他圓,就把這兩個點(圖論中的點)之間連上邊,邊權為切線長;同一圓上相鄰兩點連邊,邊權為圓上距離,然後S,T分別向每個圓作切線如果不和其他圓相交就連邊。這樣的話點數、邊數是
級別的,使用Dijkstra演算法求最短路即可。時間複雜度瓶頸在於對於一條邊列舉每一個圓去check是否相交,總時間複雜度
. 奇蹟般地能跑過。
下面重點講解一下我是如何求兩圓公切線以及點和圓的切線的:
先來說兩圓公切線:因為兩圓外離所以一定有
條公切線,
內
外。
考慮先把圓按半徑從大到小排序,現在要從半徑大的圓向半徑小的圓作
條切線。
設兩圓圓心(已知)分別為
, 圓心的直線距離
, 半徑分別為
, 一條外公切線為
(未知)。
過
作
於
, 則
為矩形。
,
, 又
, 可用勾股定理算出切線長
. 同時有
, 可利用acos
函式算出
的值。然後求出向量
即可,其中
為
逆時針旋轉
的向量。
另外一條外公切線同理,把旋轉度數取反即可。
程式碼如下:
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);
對於兩條內公切線,只要 然後計算即可。注意內公切線的向量是一加一減。
//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);
然後再來看過一點作圓的兩條切線:
和剛才的做法類似,逆時針旋轉向量
角即可。然後再除以
乘以