【POJ3608】Bridge Across Islands(旋转卡壳求两凸多边形的最短间距)

题目地址:http://poj.org/problem?id=3608

解题思路:


注意:给出的点是按顺时针的,如果不是的话需要按照顺时针/逆时针排序

贴一下网上比较流行的原理解释:

    1. 计算凸包P在y轴方向上的最小值记为yminP,和凸包Q在y轴方向上的最大值记为ymaxQ。
    2. 建立两条紧贴着yminP, ymaxQ的两条水平的直线LP, LQ。要求他们指向不同的方向。这时候他们就形成了一对anti-podal pair。
    3. 计算(yminP,ymaxQ)的距离,并记为minimum.
    4. 将两条直线顺时针旋转,直到其中一条遇到凸包的一条边停止。
    5. 只要有一条直线遇到了一条边,我们就需要计算新的vertex-vertex anti-podal之间的距离,并同minimum比较,并更新。如果两条直线都分同一条边重合,那么情况就复杂一些了。如果这两条边”重合“,也就是说我们可以画一条垂直于这两条直线的直线并且和凸包上的两个边都相交(不包括在顶点相交),那么我们就需要计算两条直线的距离了。否则的话我们只要计算3个新的顶点到顶点之间的距离。所有的具体都要同minimum比较,并更新minimum 的值。
    6. 重复步骤4,5,直到两条直线又回到起始位置为止。
    7. 输出最小的距离。
    
    # 之所以在最后旋转卡壳求2次是因为旋转卡壳要求两个直线都已经回到原点后才能结束。但是由于具体代码实现时,结束时不一定保证两条
      直线都已经回到原点(因为一个凸包的点数可能较多)。所以可以将两个凸包互换后再处理一次,就能保证正确性。
原文链接:https://blog.csdn.net/qq_29169749/article/details/58306965

但是原理是原理,代码实现起来我觉得不是很好理解,大概能半懂吧。当成模版来记吧。

核心代码中的这两行代码有点不太好理解,其实是用叉积代替了面积来计算,同底,叉积越大高越大,这个高就是点到线的垂直距离。之所以没有加fabs,一是避免超时,二是因为调用了两次这个函数,对最终结果并无影响。代码的时间复杂度看起来是O(n^2)级别的,实际上是线性的,while循环的次数很少。

while(Cross(v, ch2[miny+1]-ch1[maxy]) - Cross(v, ch2[miny]-ch1[maxy]) > eps)
     miny = (miny + 1) % m;

附一个简单样例,有利于搞懂程序:

 5 4
0 1
3 4
4 2
3 1
2 0
4 6
6 7
8 5
6 4
0 0

ac代码:


#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const double eps = 1e-8;
const double pi = acos(-1.0);
const int maxn = 1e4+10;
const double INF = 0x3f3f3f3f * 1.0;
int dcmp(double x)//精度三态函数(>0,<0,=0)
{
    if (fabs(x) < eps)return 0; //等于
    else return x < 0 ? -1 : 1;//小于,大于
}
struct Point
{
    double x,y;
    Point(double x=0,double y=0):x(x),y(y){}
    friend bool operator < (Point a, Point b)
    {
        return a.x == b.x ? a.y < b.y : a.x < b.x;
    }
    friend bool operator == (Point a, Point b)
    {
        return dcmp(a.x-b.x) == 0 && dcmp(a.y-b.y) == 0;
    }
};
typedef Point Vector;
Vector operator + (Vector a, Vector b)//向量加法
{
    return Vector(a.x + b.x, a.y + b.y);
}
Vector operator - (Vector a, Vector b)//向量减法
{
    return Vector(a.x - b.x, a.y - b.y);
}

double Dot(Vector a, Vector b)//点积
{
    return a.x * b.x + a.y * b.y;
}
double Cross(Vector a, Vector b)//外积
{
    return a.x * b.y - a.y * b.x;
}
double Length(Vector a)//模
{
    return sqrt(Dot(a, a));
}

double DistanceToSegment(Point P, Point A, Point B)//点P到AB所在线段的距离
{
    if(A == B) return Length(P - A);
    Vector v1 = B - A, v2 = P - A, v3 = P - B;
    if(dcmp(Dot(v1, v2) < 0)) return Length(v2);//第一种情况
    else if(dcmp(Dot(v1, v3)) > 0) return Length(v3);//第二种情况
    else return fabs(Cross(v1, v2)) / Length(v1);//三
}
double DistanceBetLines(Point A, Point B, Point C, Point D)
{
    double a = min(DistanceToSegment(A,C,D), DistanceToSegment(B,C,D));
    double b = min(DistanceToSegment(C,A,B), DistanceToSegment(D,A,B));
    return min(a, b);
}
double DistanceBetPolygons(Point ch1[], Point ch2[], int n, int m)
{
    double ans = INF;
    int maxy = 0, miny = 0;
    for(int i = 0; i < n; i++) if(ch1[i].y > ch1[maxy].y) maxy = i;
    for(int i = 0; i < m; i++) if(ch2[i].y < ch2[miny].y) miny = i;
    ch1[n] = ch1[0];
    ch2[m] = ch2[0];
    for(int i = 0; i < n; i++)
    {
        Vector v = ch1[maxy+1] - ch1[maxy];
        while(Cross(v, ch2[miny+1]-ch1[maxy]) - Cross(v, ch2[miny]-ch1[maxy]) > eps)
            miny = (miny + 1) % m;
        ans = min(ans, DistanceBetLines(ch1[maxy], ch1[maxy+1], ch2[miny], ch2[miny+1]));
        maxy = (maxy + 1) % n;
    }
    return ans;
}
int n, m;
Point p1[maxn], p2[maxn];
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    while(scanf("%d %d",&n, &m))
    {
        if(n == 0 && m == 0) break;
        for(int i = 0; i < n; i++) scanf("%lf %lf", &p1[i].x, &p1[i].y);
        for(int i = 0; i < m; i++) scanf("%lf %lf", &p2[i].x, &p2[i].y);
        printf("%.5lf\n", min(DistanceBetPolygons(p1, p2, n, m),DistanceBetPolygons(p2, p1, m, n)));
    }
    return 0;
}
发布了299 篇原创文章 · 获赞 81 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Cassie_zkq/article/details/101036022