poj 2187 旋转卡壳(平面上最远点对)

题意:给定二维平面上不超过50000个点,求最远点对距离的平方。

思路:由数据量来判断直接枚举会超时。注意到最远距离点对必出现在这些点的凸包上,所以可以先求出凸包,然后在凸包上枚举。此法的最坏情况复杂度仍然是n^2的,但是可以AC这道题了。在复杂度意义下的优化是旋转卡壳(参考http://www.cppblog.com/staryjy/archive/2009/11/19/101412.html):

poj 2187 旋转卡壳(平面上最远点对)_第1张图片

如果qa,qb是凸包上最远两点,必然可以分别过qa,qb画出一对平行线。通过旋转这对平行线,我们可以让它和凸包上的一条边重合,如图中蓝色直线,可以注意到,qa是凸包上离p和qb所在直线最远的点。于是我们的思路就是枚举凸包上的所有边,对每一条边找出凸包上离该边最远的顶点,计算这个顶点到该边两个端点的距离,并记录最大的值。直观上这是一个O(n2)的算法,和直接枚举任意两个顶点一样了。但是注意到当我们逆时针枚举边的时候,最远点的变化也是逆时针的,这样就可以不用从头计算最远点,而可以紧接着上一次的最远点继续计算(详细的证明可以参见上面链接中的论文)。于是我们得到了O(n)的算法。


凸包上枚举:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstdlib>
using namespace std;
#define clc(s,t) memset(s,t,sizeof(s))
#define INF 0x3fffffff
#define N 50005
struct point{
    int x,y;
}p[N];
int n;
int stack[N],top = -1;
int multi(struct point a,struct point b,struct point c){
    return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
int dis(struct point a,struct point b){
    return (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);
}
int cmp(struct point a,struct point b){
    int tmp = multi(p[1],a,b);
    if(tmp == 0)
        return dis(p[1],a) < dis(p[1],b);
    return tmp>0;
}
int main(){
    int i,j,res=0;
    struct point begin;
    scanf("%d",&n);
    begin.x = begin.y = 10005;
    for(i = 1;i<=n;i++){
        scanf("%d %d",&p[i].x,&p[i].y);
        if(p[i].y < begin.y){
            begin = p[i];
            j = i;
        }else if(p[i].y==begin.y && p[i].x<begin.x){
            begin = p[i];
            j = i;
        }
    }
    p[j] = p[1];
    p[1] = begin;
    sort(p+2,p+n+1,cmp);
    stack[++top] = 1;
    stack[++top] = 2;
    for(i = 3;i<=n;i++){
        while(top>0 && multi(p[stack[top-1]], p[stack[top]], p[i])<=0)
            top--;
        stack[++top] = i;
    }
    for(i = 0;i<top;i++)
        for(j = i+1;j<=top;j++)
            res = max(res,dis(p[stack[i]],p[stack[j]]));
    printf("%d\n",res);
                            
}

旋转卡壳:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstdlib>
using namespace std;
#define clc(s,t) memset(s,t,sizeof(s))
#define INF 0x3fffffff
#define N 50005
struct point{
    int x,y;
}p[N];
int n;
int stack[N],top = -1;
int multi(struct point a,struct point b,struct point c){
    return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
int dis(struct point a,struct point b){
    return (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y);
}
int cmp(struct point a,struct point b){
    int tmp = multi(p[1],a,b);
    if(tmp == 0)
        return dis(p[1],a) < dis(p[1],b);
    return tmp>0;
}
int main(){
    int i,j,res=0;
    struct point begin;
    scanf("%d",&n);
    begin.x = begin.y = 10005;
    for(i = 1;i<=n;i++){
        scanf("%d %d",&p[i].x,&p[i].y);
        if(p[i].y < begin.y){
            begin = p[i];
            j = i;
        }else if(p[i].y==begin.y && p[i].x<begin.x){
            begin = p[i];
            j = i;
        }
    }
    if(n==2){
        printf("%d\n",dis(p[1],p[2]));
        return 0;
    }
    p[j] = p[1];
    p[1] = begin;
    sort(p+2,p+n+1,cmp);
    stack[++top] = 1;
    stack[++top] = 2;
    for(i = 3;i<=n;i++){
        while(top>0 && multi(p[stack[top-1]], p[stack[top]], p[i])<=0)
            top--;
        stack[++top] = i;
    }//至此用graham法求得凸包,下面是旋转卡壳法
    
    j = 1;
    stack[++top] = 1;
    for(i = 0;i<top;i++){
        while(multi(p[stack[i]],p[stack[i+1]],p[stack[j+1]]) > multi(p[stack[i]], p[stack[i+1]], p[stack[j]]))
            j = (j+1)%top;//逆时针枚举距离线段(p[stack[i]],p[stack[i+1]])最远的点j
        res = max(res,dis(p[stack[i]],p[stack[j]]));
    }
    printf("%d\n",res);
                            
}


你可能感兴趣的:(poj 2187 旋转卡壳(平面上最远点对))