[Luogu P3187] [HNOI2007]最小矩形覆盖

洛谷传送门

题目描述

给定一些点的坐标,要求求能够覆盖所有点的最小面积的矩形,输出所求矩形的面积和四个顶点坐标

输入输出格式
输入格式:

第一行为一个整数n(3<=n<=50000),从第2至第n+1行每行有两个浮点数,表示一个顶点的x和y坐标,不用科学计数法

输出格式:

第一行为一个浮点数,表示所求矩形的面积(精确到小数点后5位),接下来4行每行表示一个顶点坐标,要求第一行为y坐标最小的顶点,其后按逆时针输出顶点坐标.如果用相同y坐标,先输出最小x坐标的顶点

输入输出样例
输入样例#1:

6 1.0 3.00000

1 4.00000

2.0000 1

3 0.0000

3.00000 6

6.0 3.0

输出样例#1:

18.00000

3.00000 0.00000

6.00000 3.00000

3.00000 6.00000

0.00000 3.00000

解题分析

首先, 据说有那么个结论:最小矩形的一边一定在凸包上(博主不会证TAT)。
那么, 我们可以枚举每个点及其下一个点构成的边, 再来找其他三条边。
显然矩形的左边和右边分别为点积最小和最大的地方,而上底为叉积最大的地方, 并且叉积、点积的大小具有单峰性,所以我们可以O(N)求出最小面积。再加上凸包的 ONlogN O ( N l o g N ) ,总复杂度为 ONlogN O ( N l o g N )

代码如下:

#include <cstdio>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iomanip>
#define Re register
#define W while
#define IN inline
#define gc getchar()
namespace Geometry
{
    #define db double
    #define EPS 1e-8
    #define MX 50005
    using std::sort;
    using std::min;
    using std::max;
    struct pt
    {
        db x, y;
    };
    IN pt operator + (const pt &x, const pt &y){return (pt){x.x + y.x, x.y + y.y};}
    IN pt operator - (const pt &x, const pt &y){return (pt){x.x - y.x, x.y - y.y};}
    IN db operator * (const pt &x, const pt &y){return x.x * y.y - x.y * y.x;}//cross_mul
    IN db operator / (const pt &x, const pt &y){return x.x * y.x + x.y * y.y;}//dot_mul
    IN pt operator * (const db &mul, const pt &x){return (pt){x.x * mul, x.y * mul};}   
    IN pt operator * (const pt &x, const db &mul){return (pt){x.x * mul, x.y * mul};}
    IN int sig (const db &x){return (x > -EPS) - (x < EPS);}
    IN bool operator < (const pt &x, const pt &y){return !sig(x.y - y.y) ? x.x < y.x : x.y < y.y;}
    IN db dis(const pt &x){return sqrtl(x.x * x.x + x.y * x.y);}
    int top, dot;
    pt data[MX], con[MX], ang[5];
    void get_con()
    {
        sort(data, data + dot);
        for (Re int i = 0; i < dot; ++i)
        {
            W (top > 1 && ((con[top - 1] - con[top - 2]) * (data[i] - con[top - 2])) <= 0) --top;
            con[top++] = data[i];
        }
        int limit = top;
        for (Re int i = dot - 2; i >= 0; --i)
        {
            W (top > limit && ((con[top - 1] - con[top - 2]) * (data[i] - con[top - 2])) <= 0) --top;
            con[top++] = data[i];
        }
        if(dot > 1) top--;
    }
    void get_rec()
    {
        con[top] = con[0];
        db ans = 1e60;
        db now;
        int up = 1, lef = 1, rig = 1;
        db D, H, R, L;
        for (Re int i = 0; i < top; ++i)
        {
            D = dis(con[i + 1] - con[i]);
            W ((con[i + 1] - con[i]) * (con[up] - con[i]) - (con[i + 1] - con[i]) * (con[up + 1] - con[i]) < EPS) up = (up + 1) % top;//找右边的端点
            W ((con[i + 1] - con[i]) / (con[rig] - con[i]) - (con[i + 1] - con[i]) / (con[rig + 1] - con[i]) < EPS) rig = (rig + 1) % top;//上端端点
            if(!i) lef = rig;// 第一次时要将lef起点放到上部点去,
            //否则因为单增的缘故无法到达正确的点
            W ((con[i + 1] - con[i]) / (con[lef] - con[i]) - (con[i + 1] - con[i]) / (con[lef + 1] - con[i]) > -EPS) lef = (lef + 1) % top;
            L = (con[i + 1] - con[i]) / (con[lef] - con[i]) / D;
            R = (con[i + 1] - con[i]) / (con[rig] - con[i]) / D;
            H = (con[i + 1] - con[i]) * (con[up] - con[i]) / D;
            if (H < 0) H = -H;
        //  printf("Now is the point %lf %lf and L = %lf, H = %lf, R = %lf\n",con[i].x, con[i].y, L, H, R);
            now = (R - L) * H;
            if (now < ans)//更新答案, 按逆时针方向存储
            {
                ans = now;
                ang[0] = (R / D) * (con[i + 1] - con[i]) + con[i];
                ang[1] = ang[0] + (con[rig] - ang[0]) * (H / dis(con[rig] - ang[0]));
                ang[2] = ang[1] - (ang[0] - con[i]) * ((R - L) / (dis(con[i] - ang[0])));
                ang[3] = ang[2] - (ang[1] - ang[0]);
            }
        }
        printf("%.5lf\n", ans);
    }
}
using namespace Geometry;
int main()
{
    scanf("%d", &dot);
    for (Re int i = 0; i < dot; ++i)
        scanf("%lf%lf", &data[i].x, &data[i].y);
    get_con();
    get_rec();
    int fir = 0;//确定从哪个点开始输出
    for (Re int i = 1 ; i <= 3; ++i)
    {
        if (ang[i] < ang[fir]) fir = i;
    }
    for (Re int i = 0; i <= 3; ++i)
    {
        if(!sig(ang[i].x)) ang[i].x = fabs(ang[i].x);
        if(!sig(ang[i].y)) ang[i].y = fabs(ang[i].y);
    }
    for (Re int i = 0; i <= 3; ++i)
    {
        printf("%.5lf %.5lf\n", ang[(fir + i) % 4].x, ang[(fir + i) % 4].y );
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值