這是一道來自《算法筆記》的題目
題目描述
給定 N 個線段的長度,試將它們頭尾相接(順序任意)地組合成一個凸多邊形,使得凸多邊形的外接圓的半徑最大,求該最大半徑。其中 N 不超過 105 ,線段長度均不超過 100 ,要求算法中不涉及坐標的計算。
考查內容
二分算法的本質就是通過不斷迭代使left 和 right 在固定條件下逐漸靠近真實值,符合一定誤差,所以實際上把該題放在二分擴展里面,這個所謂的最大半徑的“最大”是不在求解中的,最大應該算題干,先組成一個有外接圓的凸多邊形,然后求其半徑即可。不要誤入歧途在“最大”上絞盡腦汁。
分析
- 首先半徑一定大於等於最長線段的一半。
- 這題的意思是這些線段組成的凸多邊形的頂點一定能放在某個圓上,我們只需要考慮半徑最大就行,不用考慮如何組成的這種圖形。
- 外接圓圓心與每個線段頂點連接后會有一個圓心角
- 如果圓心在凸多邊形的內部,假設最大半徑是rmax,則所有圓心角之和應該為2π。
-
- 如果半徑比rmax大,那么這些線段的圓心角之和sum<2∗π,線段沒法首尾相連的在外接圓上,半徑應變小。
- 如果半徑比rmax小,那么這些線段的圓心角之和sum>2∗π,這時構成的外接圓不是半徑最大的外接圓,半徑應變大。
sum為綠色之和
因此可以對半徑長度二分,尋找圓心角之和為2∗π2*\pi2∗π的那個值即可。
2. 如果圓心在凸多邊形的外部,假設最大半徑是rmax,那么最長的線段的圓心角,一定等於其他線段的圓心角之和。取計算目標為其他圓心角和+2π−最大圓心角,此時情況取反。
代碼(引用https://blog.csdn.net/flashmsn/article/details/94642687)
#include<cstdio> #include<cmath> const double PI=acos(-1.0); const double eps=1e-5;//比較精度 //求圓心角之和 double totalCornerAngles(double edges[],int n,double r){ double sum = 0.0; for(int i =0;i<n;i++) sum+=asin(edges[i]/2/r)*2; return sum; } int main(){ int N;//邊數 scanf("%d",&N);//輸入邊數 double edges[100];//邊長數組 double sum;//圓心角之和 double maxAngle=0.0;//最長邊對應的圓心角 double maxEdge=0.0;//最長邊 //初始化edges for(int i=0;i<N;i++){ scanf("%lf",&edges[i]); if(edges[i]>maxEdge) maxEdge = edges[i];//保存最大邊 } //以最長邊為直徑求圓心角之和,若為2π則直接返回 sum = totalCornerAngles(edges,N,maxEdge/2); if(fabs(sum-PI*2)<eps){ printf("外接圓的最大半徑是最大邊的一半:%.2f",maxEdge/2); return 0 ; } //半徑大於最大邊的一半(即斜邊大於直角邊) double left =maxEdge/2,right=10000000,mid; double other=0; //在誤差范圍內循環求解 while(right -left >eps){ mid = (right + left) /2; maxAngle=asin(maxEdge/2/mid)*2;//求出最大邊對應的圓心角 sum = totalCornerAngles(edges,N,mid); other=sum-maxAngle; //如果除去最大圓心角的其他圓心角之和小於π,說明圓心在多邊形外面 if(other<PI){ sum=other+2*PI-maxAngle; if( sum<2*PI) left = mid; else right = mid; } //圓心在多邊形里面 else{ if(sum>2*PI) left = mid; else right = mid; } } printf("外接圓的最大半徑是:%.2lf",mid); return 0; }
參考:https://blog.csdn.net/liuerin/article/details/98961797