p1257 平面上最接近点对---(分治法)
首先就是一维最接近点的情况。。。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 #define INF 0x3f3f3f3f 7 double s[100]; 8 double ans=0; 9 10 int main(){ 11 int n; 12 while(~scanf("%d",&n)){ 13 for( int i=0; i<n; i++ ){ 14 scanf("%lf",s+i); 15 } 16 sort(s,s+n); 17 ans=INF; 18 for( int i=0; i<n; i++ ){ 19 ans=(s[i+1]-s[i]>ans)?ans:(s[i+1]-s[i]); 20 } 21 printf("%f\n",ans); 22 23 } 24 return 0; 25 }
很显然这是暴力求解的方法。。。
但是这种方法不适合推移到二维方面,因而推荐使用分治法进行求解没时间复杂度O(nlogn)。。。
使用分治求解:
S中的n个点为x轴上的n个实数x1,x2,...,xn。最接近点对即为这n个实数中相差最小的两个实数。显然可以先将点排好序,然后线性扫描就可以了(上述程序实现)。但我们为了便于推广到二维的情形,为下面二维,尝试用分治法解决这个问题。
假设我们用m点将S分为S1和S2两个集合,这样一来,对于所有的p(S1中的点)和q(S2中的点),有p<q。
递归地在S1和S2上找出其最接近点对{p1,p2}和{q1,q2},并设
d = min{ |p1-p2| , |q1-q2| }
由此易知,S中最接近点对或者是{p1,p2},或者是{q1,q2},或者是某个{q3,p3},如下图所示。
如果最接近点对是{q3,p3},即|p3-q3|<d,则p3和q3两者与m的距离都不超过d,且在区间(m-d,d]和(d,m+d]各有且仅有一个点。这样,就可以在线性时间内实现合并。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<ctime> 5 #include<algorithm> 6 using namespace std; 7 #define INF 0x3f3f3f3f 8 double s[100]; 9 double ans=0; 10 11 double closest(int low,int high){ 12 if(low+1==high) return s[high]-s[low]; 13 if(low+2==high) return min(s[low+1]-s[low],s[high]-s[low+1]); 14 int mid=(low+high)>>1; 15 double tmp=min(closest(low,mid),closest(mid+1,high)); 16 if(s[mid+1]-s[mid]<tmp) tmp=s[mid+1]-s[mid]; 17 return tmp; 18 } 19 20 int main(){ 21 int n,m; 22 srand((unsigned)time(NULL)); 23 while(~scanf("%d",&n)){ 24 for( int i=0; i<n; i++ ){ 25 s[i]=rand()%10000; 26 printf("%f",s[i]); 27 } 28 printf("\n"); 29 sort(s,s+n); 30 for( int i=0; i<n; i++ ){ 31 printf("%f ",s[i]); 32 } 33 printf("\n"); 34 printf("%.4f\n",closest(0,n-1)); 35 } 36 return 0; 37 }
接下来二为最接近点对的情况。。。
分治:
1) 把它分成两个或多个更小的问题;
2) 分别解决每个小问题;
3) 把各小问题的解答组合起来,即可得到原问题的解答。小问题通常与原问题相似,可以递归地使用分而治之策略来解决。
其实,这里 用到了分治的思想。将所给平面上n个点的集合S分成两个子集S1和S2,每个子集中约有n/2个点。然后在每个子集中递归地求最接近的点对。在这里,一个 关键的问题是如何实现分治法中的合并步骤,即由S1和S2的最接近点对,如何求得原集合S中的最接近点对。如果这两个点分别在S1和S2中,问题就变得复 杂了。
在二维的情况下:
我们仿照一维的情况先把所有点按照x(横坐标)从左到右升序排列.
以X横坐标中间的点作为分界线.将平面的点分成左边和右边,以上图为例,分为左边5个右边5个.
然后找到左边的点中最近点对的距离d1,和右边最近点对的距离d2。
令d=min{d1,d2}.那么d是当前整个平面的最近点对的距离吗?
答案不是!!
当前的d1,d2都是两边各自的点的最近距离,但是没有考虑到两边的点相互配对的情况,即是点对一个在左边区域,一个在右边区域.
如果我们这个时候把两边相互配对的所有情况全部罗列出来那么时间复杂度就变为第一种方法的O(n2).
这个时候我们便可以用上面找到的d来限制配对.即是明显超过d的两点不需要配对.如下:
那么在范围内的只有三个点,也就是说只有这个三个点相互配对的距离才可能比d小.那么再按照方法一一样在这些点中找到最短距离再跟d去比较.小的就是当前整个平面中所考虑的所有点中最近点对的距离。
然而在以上问题处理上,有一个问题尚未解决就是如何找到两边区域中的最近点对的距离d1,d2 ?
我们可以发现在处理上面这个问题的时候,和最开始处理所有平面的点最近点距离的问题类似,只是点的数目减半了.
那么我们可以递归以上问题.直到划分的区域中只有二个或者三个点.这样,该区域的最近点对距离就是对应的距离。这也是递归终止的条件。
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 const int maxn=1e4+10; 8 9 struct point { 10 double x,y; 11 }p[maxn]; 12 int a[maxn]; 13 14 bool cmpx(point a,point b){ 15 return a.x<b.x; 16 } 17 18 bool cmpy(int a,int b){ 19 return p[a].y<p[b].y; 20 } 21 22 double dis(point a,point b){ 23 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); 24 } 25 26 double closest(int low,int high){ 27 if(low+1==high) return dis(p[low],p[high]); 28 if(low+2==high) return min(dis(p[low],p[high]),min(dis(p[low],p[low+1]),dis(p[low+1],p[high]))); 29 30 int mid=(low+high)>>1; 31 double ans=min(closest(low,mid),closest(mid+1,high)); 32 33 int cnt=0; 34 for( int i=low; i<=high; i++ ){ 35 if(p[i].x>=p[mid].x-ans&&p[i].x<=p[mid].x+ans){ 36 a[cnt++]=i; 37 } 38 } 39 sort(a,a+cnt,cmpy); 40 for( int i=0; i<cnt; i++ ){ 41 for( int j=i+1; j<cnt; j++ ){ 42 if(p[a[j]].y-p[a[i]].y>=ans) break; 43 ans=min(ans,dis(p[a[j]],p[a[i]])); 44 } 45 } 46 return ans; 47 } 48 49 int main(){ 50 int n; 51 while(~scanf("%d",&n)){ 52 for( int i=0; i<n; i++ ){ 53 scanf("%lf%lf",&p[i].x,&p[i].y); 54 } 55 sort(p,p+n,cmpx); 56 57 printf("%.4f\n",closest(0,n-1)); 58 } 59 return 0; 60 }