【模板】最小矩形覆盖

前置芝士:凸包,旋转卡壳,向量的基本运算

题意

给出平面上的一堆点,找出一个能够覆盖所有点的面积最小的矩形,输出面积及四个顶点的坐标。

分析

一眼计算几何好毒瘤题。(废话)

经过一番涂涂画画之后,我们可以非常感性地得出一个结论:最后这个矩形一定有一边和这些点的凸包上的一条边重合。

也就是说,最后的矩形应该是长成下面这样子的。

VlElb8.png

那么我们很显然需要先求出凸包,然后枚举凸包上的一条边来和矩形底边重叠。

接着观察这张图,我们可以发现还需要三个点来确定这个矩形:也就是最上方的点 E E E(即底边的对踵点)、最左边和最右边的点 F , C F,C F,C。只要找到这三个点,就可以确定这一个矩形。

其中最容易求的应该是最上面的那个点,其实就是旋转卡壳的模板,用叉积判断一下点到直线的距离即可。

然后是左右两个点。这两个点大体框架和旋转卡壳差不多,也是维护单调队列,逆时针遍历凸包上的点,唯一有区别的就是判断条件了。

仔细观察上面的图,当逆时针遍历时,遍历到最右点之前,所有凸包上的边与底边(如 A B ⃗ \vec{AB} AB B C ⃗ \vec{BC} BC )的点积始终为正;但是过了最右点之后(如 C D ⃗ \vec{CD} CD ),点积变为负;接着直到过了最左点,才又变为正。

所以问题就很简单了,我们套用旋转卡壳的板子,把中间用叉积判断的部分改成点积判断,就可以找出最左点和最右点。


找出三个点之后,我们考虑怎么求矩形的面积和顶点坐标。由于矩形的高和底边垂直,那么我们可以先求出底边的垂线。那么就可以结合左右点求出矩形的左右两边所在的直线,再结合上下两点所确定的高,就可以很方便地求出矩形的面积和顶点。(具体可以结合代码理解)

代码

先上板子题:P3187 最小矩形覆盖

#include <bits/stdc++.h>
#define MAX 100005
#define eps 1e-8
using namespace std;

struct vec{
    
    
    double x, y;

    friend bool operator <(vec a, vec b){
    
    		//真·小于
        if(fabs(a.x-b.x) > eps) return a.x < b.x;
        return a.y < b.y;
    }
    friend bool operator <= (vec a, vec b){
    
    		//用来逆时针输出
        if(fabs(a.y-b.y) > eps) return a.y < b.y;
        return a.x < b.x;
    }

    friend vec operator +(vec a, vec b){
    
    
        return (vec){
    
    a.x+b.x, a.y+b.y};
    }
    friend vec operator -(vec a, vec b){
    
    
        return (vec){
    
    a.x-b.x, a.y-b.y};
    }
    friend vec operator ^(vec a, double b){
    
    		//数乘
        return (vec){
    
    a.x*b, a.y*b};
    }
    friend double operator %(vec a, vec b){
    
    		//点积
        return a.x*b.x+a.y*b.y;
    }
    friend double operator *(vec a, vec b){
    
    		//叉积
        return a.x*b.y-a.y*b.x;
    }
}a[MAX], h[MAX];

double dis(vec a, vec b){
    
               //两点之间的距离
    return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}

double dis(vec a, vec b, vec c){
    
        //c到直线ab的距离
    return fabs((c-a)*(c-b)/dis(a, b));
}

vec inter(vec a, vec b, vec c, vec d){
    
           //直线ab和cd的交点
    double s = (c-a)*(d-a);
    double S = s+(d-b)*(c-b);
    return a+((b-a)^(s/S));
}

int n;
int st[MAX], top, vis[MAX];
vec pt[5];

int main()
{
    
    
    cin >> n;
    for(int i = 1; i <= n; ++i){
    
    
        scanf("%lf%lf", &a[i].x, &a[i].y);
    }
    sort(a+1, a+n+1);
    st[++top] = 1;
    for(int i = 2; i <= n; ++i){
    
    		//求凸包
        while(top > 1 && (a[st[top]]-a[st[top-1]]) * (a[i]-a[st[top]]) <= 0){
    
    
            vis[st[top]] = 0;
            top--;
        }
        vis[i] = 1;
        st[++top] = i;
    }
    int tmp = top;
    for(int i = n-1; i >= 1; --i){
    
    		
        if(vis[i]) continue;
        while(top > tmp && (a[st[top]]-a[st[top-1]]) * (a[i]-a[st[top]]) <= 0){
    
    
            vis[st[top]] = 0;
            top--;
        }
        vis[i] = 1;
        st[++top] = i;
    }
    for(int i = 1; i <= top; ++i){
    
    
        h[i] = a[st[i]];
    }
    top--;
    int l, r = 1, t = 1;
    while(dis(h[1], h[2], h[t]) <= dis(h[1], h[2], h[t+1])){
    
    
        t = t%top+1;
    }
    l = t; //确定最左点的初始位置,因为最左点必须在最右点的后面,否则初始值为1就会找到错误的点积大于0的点
    double ans = 1e18;
    for(int i = 1; i <= top; ++i){
    
    
        //找出三个关键点,套用旋转卡壳板子
        while((h[r+1]-h[r]) % (h[i+1]-h[i]) >= 0)
            r = r%top+1;
        while(dis(h[i], h[i+1], h[t]) <= dis(h[i], h[i+1], h[t+1]))
            t = t%top+1;
        while((h[l+1]-h[l]) % (h[i+1]-h[i]) <= 0)
            l = l%top+1;
        vec cx = (vec){
    
    -(h[i+1]-h[i]).y, (h[i+1]-h[i]).x};      //垂线
        double area = dis(h[i], h[i+1], h[t])*dis(h[r], h[r]+cx, h[l]);	//求面积
        if(area < ans){
    
    
            ans = area;
            //利用直线间的交点求出顶点坐标
            pt[1] = inter(h[i], h[i+1], h[r], h[r]+cx);
            pt[2] = inter(h[r], h[r]+cx, h[t], h[t]+(h[i+1]-h[i]));
            pt[3] = inter(h[t], h[t]+(h[i+1]-h[i]), h[l], h[l]+cx);
            pt[4] = inter(h[l], h[l]+cx, h[i], h[i+1]);
        }
    }
    for(int i = 1; i <= 4; ++i){
    
    		//防止出现-0.00000
        if(fabs(pt[i].x) < eps) pt[i].x = 0.0;
        if(fabs(pt[i].y) < eps) pt[i].y = 0.0;
    }
    int mn = 1;
    for(int i = 2; i <= 4; ++i){
    
    			//保证逆时针输出
        if(pt[i] <= pt[mn]) mn = i;
    }
    printf("%.5lf\n", ans);
    for(int i = mn; i <= 4; ++i)
        printf("%.5lf %.5lf\n", pt[i].x, pt[i].y);
    for(int i = 1; i < mn; ++i)
        printf("%.5lf %.5lf\n", pt[i].x, pt[i].y);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_30115697/article/details/90717223