题目描述
给定一些点的坐标,要求求能够覆盖所有点的最小面积的矩形,输出所求矩形的面积和四个顶点坐标
输入输出格式
输入格式:
第一行为一个整数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)求出最小面积。再加上凸包的
O(NlogN)
O
(
N
l
o
g
N
)
,总复杂度为
O(NlogN)
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;
}