旋转卡壳入门二之 POJ 3608

POJ 3608 Bridge Across Islands

题目链接
Thousands of thousands years ago there was a small kingdom located in the middle of the Pacific Ocean. The territory of the kingdom consists two separated islands. Due to the impact of the ocean current, the shapes of both the islands became convex polygons. The king of the kingdom wanted to establish a bridge to connect the two islands. To minimize the cost, the king asked you, the bishop, to find the minimal distance between the boundaries of the two islands.
这里写图片描述

Input
The input consists of several test cases.
Each test case begins with two integers N, M. (3 ≤ N, M ≤ 10000)
Each of the next N lines contains a pair of coordinates, which describes the position of a vertex in one convex polygon.
Each of the next M lines contains a pair of coordinates, which describes the position of a vertex in the other convex polygon.
A line with N = M = 0 indicates the end of input.
The coordinates are within the range [-10000, 10000].

Output
For each test case output the minimal distance. An error within 0.001 is acceptable.

Sample Input
4 4
0.00000 0.00000
0.00000 1.00000
1.00000 1.00000
1.00000 0.00000
2.00000 0.00000
2.00000 1.00000
3.00000 1.00000
3.00000 0.00000
0 0
Sample Output
1.00000

题解:
题目大意就是给定两个凸包,求两个凸包的最短距离。
我们知道两个凸多边形 P 和 Q 之间的最小距离由多边形间的对踵点对确立。 存在凸多边形间的三种多边形间的对踵点对, 因此就有三种可能存在的最小距离模式:
“顶点-顶点”的情况
“顶点-边”的情况
“边-边”的情况

给定结果, 一个基于旋转卡壳的算法自然而然的产生了(官方解法描述233333):
考虑如下的算法, 算法的输入是两个分别有 m 和 n 个顺时针给定顶点的凸多边形 P 和 Q。
1、计算 P 上 y 坐标值最小的顶点(称为 yminP ) 和 Q 上 y 坐标值最大的顶点(称为 ymaxQ)。
2、为多边形在 yminP 和 ymaxQ 处构造两条切线 LP 和 LQ 使得他们对应的多边形位于他们的右侧。 此时 LP和 LQ 拥有不同的方向, 并且 yminP 和 ymaxQ 成为了多边形间的一个对踵点对。
3、计算距离(yminP,ymaxQ) 并且将其维护为当前最小值。
4、逆时针同时旋转平行线直到其中一个与其所在的多边形的边重合。
5、如果只有一条线与边重合, 那么只需要计算“顶点-边”对踵点对和“顶点-顶点”对踵点对距离。 都将他们与当前最小值比较, 如果小于当前最小值则进行替换更新。 如果两条切线都与边重合, 那么情况就更加复杂了。 如果边“交叠”, 也就是可以构造一条与两条边都相交的公垂线(但不是在顶点处相交), 那么就计算“边-边”距离。 否则计算三个新的“顶点-顶点”对踵点对距离。 所有的这些距离都与当前最小值进行比较, 若小于当前最小值则更新替换。
6、重复执行步骤4和步骤5, 直到新的点对为(yminP,ymaxQ)。
7、输出最大距离。
旋转卡壳模式保证了所有的对踵点对(和所有可能的子情况)都被考虑到。 此外, 整个算法拥有现行的时间复杂度, 因为(除了初始化), 只有与顶点数同数量级的操作步数需要执行。

下面贴出AC代码,顺便说一下几个遇到的坑,和一些疑惑,希望有大佬遇过可以解答一下

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define eps 1e-10

const int MAXN=10000+7;


struct Point{
    double x,y;
    Point(){}
    Point(double x,double y):x(x),y(y){}

    Point operator +(const Point &p){
        return Point(x+p.x,y+p.y);
    }
    Point operator -(const Point &p){
        return Point(x-p.x,y-p.y);
    }
    Point operator *(const double &d){
        return Point(x*d,y*d);
    }
    bool operator <(const Point &p){
        return x==p.x?y<p.y:x<p.x;
    }
};

Point P[MAXN],Q[MAXN];
//叉积 
double cross(Point A,Point B,Point C){
    return (B.x-A.x)*(C.y-A.y)-(B.y-A.y)*(C.x-A.x);
}
//点积 
double multi(Point A,Point B,Point C){
    return (B.x-A.x)*(C.x-A.x)+(B.y-A.y)*(C.y-A.y);
}
//两点之间得距离 
double dist(Point A,Point B){
    return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));
}

void anticlockwise(Point *p,int N){
    for(int i=0;i<N-2;++i){
        double tmp=cross(p[i],p[i+1],p[i+2]);
        if(tmp>eps)return;
        else if(tmp<-eps){
            reverse(p,p+N);
            return;
        }
    }
}
//计算点到线段得距离 
double Getdist(Point A,Point B,Point C){
    if(dist(A,B)<eps) return dist(B,C);
    if(multi(A,B,C)<-eps) return dist(A,C);
    if(multi(B,A,C)<-eps) return dist(B,C);
    return fabs(cross(A,B,C)/dist(A,B));
}
//求线段得两端点到另外一条直线得距离(即可求两条直线的距离) 
double MinDist(Point A,Point B,Point C,Point D){
    return min(min(Getdist(A,B,C),Getdist(A,B,D)),min(Getdist(C,D,A),Getdist(C,D,B)));
}

double solve(Point *P,Point *Q,int n,int m){
    int yminP=0,ymaxQ=0;  
    for(int i=0;i<n;++i)  
        if(P[i].y<P[yminP].y)  
            yminP=i;//P上y坐标最小的点  
    for(int i=0;i<m;++i)  
        if(Q[i].y>Q[ymaxQ].y)  
            ymaxQ=i;//Q上y坐标最大的顶点  
    P[n]=P[0];//为了方便避免求余  
    Q[m]=Q[0];  
    double tmp,ans=INF;
    for(int i=0;i<n;++i){
        while((tmp=cross(P[yminP+1],Q[ymaxQ+1],P[yminP])-cross(P[yminP+1],Q[ymaxQ],P[yminP]))>eps) ymaxQ=(ymaxQ+1)%m;
        ans=min(ans,MinDist(P[yminP],P[yminP+1],Q[ymaxQ],Q[ymaxQ+1]));
        yminP=(yminP+1)%n;
    }
    return ans;
}
int main(){

    int n,m;
    while(~scanf("%d%d",&n,&m)&&n&&m){
        for(int i=0;i<n;++i){
            scanf("%lf%lf",&P[i].x,&P[i].y);
        }
        for(int i=0;i<m;++i){
            scanf("%lf%lf",&Q[i].x,&Q[i].y);
        }
        anticlockwise(P,n);
        anticlockwise(Q,m);
        printf("%.5f\n",solve(P,Q,n,m)); //lf ,f等等??? 
    }
    return 0;
}

疑惑:逆时针排序点时,如果点的输入是乱序的,排序的结果并不会全部都是逆序的啊(怀疑这个算法,但是网上的都一样)???这个怎么解释?

坑:关于printf输出浮点数,一开始我用%lf结果是WA,改成%f就AC了,后面查了一下某度,发现原来如此啊!

对于printf(),无论是%f还是%lf,效果都是一样的。 因为,遇到float,printf()会将float类型自动提升到double,所以不会有什么问题。 而且严格地讲,printf()并没有对于%lf的定义,虽然很多编译器会接受,所以最好使用%f(否则会遇到不可思议的错误噢!!!)。 而对于scanf(),由于接受的是指针,并没有类型提升的说法,所以对于double就应该用%lf,float就是%f。

猜你喜欢

转载自blog.csdn.net/qq_36172505/article/details/80361310
今日推荐