• 平面最近点对(学习笔记)


    先来推荐几篇.其实大家只要认真看了这些博客,就没必要看我的了QWQ.

    平面最近点对问题:在同一平面内的所有点中,找出距离最近的两个点.

    既然有模板题,还是双倍经验,那就直接来讲吧.首先暴力应该都想得到,就是(n^2)枚举所有点,算出所有点两两之间的距离,同时比较大小,记录答案.

    考虑如何优化?核心思想是分治.至于为什么要用分治,介绍算法框架的时候自然而然就明白了.

    设solve(V)为点集V中的最近点对的距离,那么,要求点集V的最近点对,我们先对其按x坐标从小到大排序,然后考虑选择一条垂直分割线,把V划分为点数尽可能相等的两部分V1和V2(说直白了就是选择点集V最中间两个点的中线),则solve(V)=min{solve(V1),solve(V2), dis(u,v)⁡|⁡u∈V1,v∈V2}.

    如何理解这个式子?关于点集V中的最近点对,无非只有三种情况,两个点都在V1中,两个点都在V2中,或者一个点在V1中,另一个点在V2中.前两种情况就是分治的子问题,我们递归下去求解就好了.那么第三种情况如何高效求解呢?

    令d=min{solve(V1),solve(V2)},那么我们可以只需要考虑已经选择好的垂直分割线往两端各延伸d的这一个区域,因为只有这个区域内的点对才可能有比d更小的距离(这里不懂的话,画个图很快就明白了)

    
    solve(V){
    	选择一条垂直分割线,它将V分割为V1,V2;
    	d=min(solve(V1),solve(V2));
    	取出垂直分割线往两端延伸d的区域内的所有点;
    	求出区域内最近距离d’;
    	return min(d,d’);
    }
    
    

    到了这里,问题只剩最后一步,如何求出区域内最近距离d’,上面那一段文字只是优化了求解的时间复杂度,仍未提到具体地如何做?对于区域内的点,我们对其按y坐标排序;对于每个点,找到y坐标与它相差不超过d的点,计算距离d’,更新答案.

    对y排序时,sort排序,总的时间复杂度(nlog_nlog_n):

    int n,b[200005];
    struct point{
        double x,y;
    }a[200005];
    bool cmpx(point x,point y){return x.x<y.x;}
    bool cmpy(int x,int y){return a[x].y<a[y].y;}
    double dis(point x,point y){
    	return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));
     }
    //l,r是点的下标,表示本次处理由第l~r个点构成的点集
    double solve(int l,int r){
        if(l==r)return 1e18;
        if(l+1==r)return dis(a[l],a[r]);
    //亲测:这一句可以不写,上面那个边界条件必须要写
        int mid=(l+r)>>1;
    //找到分割点,即点集中最中间的点
        double d=min(solve(l,mid),solve(mid+1,r));
    //分治
        int k=0;
        for(int i=l;i<=r;i++)
        	if(fabs(a[i].x-a[mid].x)<=d)b[++k]=i;
        sort(b+1,b+k+1,cmpy);
        for(int i=1;i<=k;i++)
        for(int j=i+1;j<=k;j++)
            if(a[b[j]].y-a[b[i]].y<=d)
            	d=min(d,dis(a[b[i]],a[b[j]]));
        return d;
    }
    int main(){
        n=read();
        for(int i=1;i<=n;i++)
        	scanf("%lf%lf",&a[i].x,&a[i].y);
        sort(a+1,a+n+1,cmpx);
        printf("%.4lf
    ",solve(1,n));
        return 0;
    }
    
    

    优化:对y排序时,归并排序,总的时间复杂度(nlog_n)(亲测:快了100+ms)

    int n;
    struct point{
        double x,y;
    }a[200005],b[200005];
    bool cmpx(point x,point y){return x.x<y.x;}
    bool cmpy(point x,point y){return x.y<y.y;}
    double dis(point x,point y){
    	return sqrt((x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y));
    }
    double solve(int l,int r){
        if(l==r)return 1e18;
        int mid=(l+r)>>1;
        double midline=(a[mid].x+a[mid+1].x)/2;
    //比较两份代码,就会发现,上面的分割线直接就是a[mid].x
    //而这里的分割线是(a[mid].x+a[mid+1].x)/2
    //显然此处更为严谨,这里以a[mid].x作为分割线会WA
        double d=min(solve(l,mid),solve(mid+1,r));
        int i=l,j=mid+1,k=i;
        while(i<=mid&&j<=r){
    		if(cmpy(a[i],a[j]))b[k]=a[i],i++,k++;
    		else b[k]=a[j],j++,k++;
        }
        if(i<=mid)while(i<=mid)b[k]=a[i],i++,k++;
        else while(j<=r)b[k]=a[j],j++,k++;
        for(int i=l;i<=r;i++)a[i]=b[i];
        int L=1,R=0;
        for(int i=l;i<=r;i++)
    	if(fabs(a[i].x-midline)<=d){
    	    while(L<=R&&(a[i].y-b[L].y)>=d)L++;
    //优化时间复杂度,把一定不符合要求的点先去掉再进循环.
    //这里没有的话就会TLE
    	    for(int j=L;j<=R;j++)
    			d=min(d,dis(a[i],b[j]));
    	    b[++R]=a[i];
    	}
        return d;
    }
    

    sort排序的写法比归并排序更加直接简单,后者细节较多,但时间效率更高,一般而言,sort排序就足够了.

  • 相关阅读:
    HDU-1561
    POJ 1088
    UESTC-878
    CodeForces
    HDU 5753
    HDU 1568
    二分图入门题
    二分图匹配入门题
    树形dp入门
    UVA
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10363315.html
Copyright © 2020-2023  润新知