先說下題意,很簡單,給n個點的座標,求距離最近的一對點之間距離的一半。第一行是一個數n表示有n個點,接下來n行是n個點的x座標和y座標,實數。
這個題目其實就是求最近點對的距離。主要思想就是分治。先把n個點按x座標排序,然後求左邊n/2個和右邊n/2個的最近距離,最後合并。合并要重點說一下,比較麻煩。
首先,假設點是n個,編號為1到n。我們要分治求,則找一個中間的編號mid,先求出1到mid點的最近距離設為d1,還有mid+1到n的最近距離設為d2。這裡的點需要按x座標的順序排好,並且假設這些點中,沒有2點在同一個位置。(若有,則直接最小距離為0了)。
然後,令d為d1, d2中較小的那個點。如果說最近點對中的兩點都在1-mid集合中,或者mid+1到n集合中,則d就是最小距離了。但是還有可能的是最近點對中的兩點分屬這兩個集合,所以我們必須先檢測一下這種情況是否會存在,若存在,則把這個最近點對的距離記錄下來,去更新d。這樣我們就可以得道最小的距離d了。
關鍵是要去檢測最近點對,理論上每個點都要和對面集合的點匹配一次,那效率還是不能滿足我們的要求。所以這裡要最佳化。怎麼最佳化呢?考慮一下,假如以我們所選的分割點mid為界,如果某一點的橫座標到點mid的橫座標的絕對值超過d1並且超過d2,那麼這個點到mid點的距離必然超過d1和d2中的小者,所以這個點到對方集合的任意點的距離必然不是所有點中最小的。
所以我們先把在mid為界左右一個範圍內的點全部篩選出來,放到一個集合裡。篩選好以後,當然可以把這些點兩兩求距離去更新d了,不過這樣還是很慢,萬一滿足條件的點很多呢。這裡還得繼續最佳化。首先把這些點按y座標排序。假設排序好以後有cnt個點,編號為0到cnt-1。那麼我們用0號去和1到cnt-1號的點求一下距離,然後1號和2到cnt-1號的點求一下距離。。。如果某兩個點y軸距離已經超過了d,這次迴圈就可以直接break了,開始從下一個點尋找了.
代碼:
#include<iostream>#include<cmath>#include<algorithm>using namespace std;int n;struct node{ double x; double y;}p[100005];int a[100005];double cmpx(node a,node b){ return a.x<b.x;}double cmpy(int a,int b){ return p[a].y<p[b].y;}double min(double a,double b){ return a<b?a:b;}double dis(node a,node b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}double find(int l,int r){ if(r==l+1) return dis(p[l],p[r]); if(l+2==r) return min(dis(p[l],p[r]),min(dis(p[l],p[l+1]),dis(p[l+1],p[r]))); int mid=(l+r)>>1; double ans=min(find(l,mid),find(mid+1,r)); int i,j,cnt=0; for(i=l;i<=r;i++) { if(p[i].x>=p[mid].x-ans&&p[i].x<=p[mid].x+ans) a[cnt++]=i; } sort(a,a+cnt,cmpy); for(i=0;i<cnt;i++) { for(j=i+1;j<cnt;j++) { if(p[a[j]].y-p[a[i]].y>=ans) break; ans=min(ans,dis(p[a[i]],p[a[j]])); } } return ans;}int main(){ int i; while(scanf("%d",&n)!=EOF) { if(!n) break; for(i=0;i<n;i++) scanf("%lf %lf",&p[i].x,&p[i].y); sort(p,p+n,cmpx); printf("%.2lf%\n",find(0,n-1)/2); } return 0;}