题目大意:给出若干点,求最小矩形,使得矩形能够覆盖所有点(输出面积、逆时针输出四个顶点的坐标)。
思路:显然这个矩形要覆盖它们的凸包。
一个结论:求出这些点的凸包后,最小矩形至少有一条边贴着凸包(感觉很对,不知道严谨的证明qwq)。
然后就很容易了,只要枚举哪一条边(i,i+1)作为矩形的一边,然后分别求出对边和邻边卡过哪三个点就可以了。
这个可以用指针指向三个合法点,然后随着i的逆时针移动,这三个合法点一定也顺次移动,所以总移动次数是线性的,不会超时。
假设当前边为(A,B)
求对边经过的点C的方法: 根据向量|AC×AB|先增大后减小的规律(可以理解为三角形底不变,高最大时面积最大),可以找到最高点C
求左邻边的点D的方法:根据向量|AD·AB| 在左端点最大的性质(可以理解为左端点在AB上投影最长),可以找到左端点。
右端点方法类似。
这题注意几个坑点:1.把重复的点去掉;
2.输出可能会有-0.00000的情况,所以需要改成0.00000
3.左端点和右端点除了考虑点积以外还要考虑它们是在左边还是右边,所以点积的正负也需要考虑。
#include <cstdio>
#include <cmath>
#include <algorithm>
#define db double
#define eps 1e-9
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=5e4+5;
int n,i,wh,flg;
db ans,tmp;
struct pt{
db x,y;
pt (db X=0,db Y=0) {x=X; y=Y;}
db operator ^ (const pt &B) { return x*B.x+y*B.y; }
db operator * (const pt &B) { return x*B.y-B.x*y; }
pt operator + (const pt &B) { return pt(x+B.x,y+B.y); }
pt operator - (const pt &B) { return pt(x-B.x,y-B.y); }
pt operator * (const db &c) { return pt(x*c,y*c); }
}E,a[N],c[N*2],stk[N],xtk[N],v[10],w[10]; int stp,xtp,cn;
struct seg{
pt s,e,v;
seg (pt S=pt(0,0),pt E=pt(0,0)) {s=S; e=E; v=E-S;}
}L,R,U,B;
bool cmp(const pt &A,const pt &B)
{
if (fabs(A.x-B.x)>eps) return A.x<B.x;
return A.y<B.y;
}
void Convex()
{
int tn;
sort(a+1,a+1+n,cmp);
tn=n; n=1;
rep(i,2,tn)
if (a[i].x!=a[i-1].x || a[i].y!=a[i-1].y) a[++n]=a[i]; //去除重复的点
stp=0;
rep(i,1,n) {
while (stp>1 && (a[i]-stk[stp])*(stk[stp]-stk[stp-1])<-eps) stp--;
stk[++stp]=a[i];
}
xtp=0;
rep(i,1,n) {
while (xtp>1 && (a[i]-xtk[xtp])*(xtk[xtp]-xtk[xtp-1])>eps) xtp--;
xtk[++xtp]=a[i];
}
cn=0;
rep(i,1,stp) c[++cn]=stk[i];
down(i,xtp-1,2) c[++cn]=xtk[i];
reverse(c+1,c+1+cn);
}
db S(pt A,pt B,pt C) { return fabs((A-B)*C);}
db D(pt A,pt B,pt C) { return (A-B)^C;}
pt crs(seg A,seg B) {
db S1=(B.s-A.s)*A.v,S2=A.v*B.v;
return B.s+(B.v*(S1/S2));
}
int sgn(db a,db b)
{
if (a-b<-eps) return -1;
if (fabs(a-b)<eps) return 0;
return 1;
}
void solve()
{
int l=3,r=2,mid=3,j;
rep(i,1,cn) c[cn+i]=c[i]; //复制一份,写起来不要考虑环的问题,方便一点
rep(i,1,cn)
{
E=c[i+1]-c[i]; //当前边的向量
for (;mid<cn*2 && S(c[mid+1],c[i],E)>S(c[mid],c[i],E);mid++);
for (;(l<cn+i) && ( (-D(c[l+1],c[i],E)>-D(c[l],c[i],E) ) || (D(c[l],c[i],E)>0) );l++); //尤其注意左端点落到了右方的情况,这时左端点需要继续逆时针转动直到落到最左端
for (;(r<mid) && (D(c[r+1],c[i],E)>D(c[r],c[i],E));r++);
L=seg(c[l],c[l]+pt(E.y,-E.x)); //求出四条边的向量
R=seg(c[r],c[r]+pt(E.y,-E.x));
U=seg(c[mid],c[mid]+pt(E.x,E.y));
B=seg(c[i],c[i+1]);
v[0]=crs(B,R); v[1]=crs(R,U); v[2]=crs(U,L); v[3]=crs(L,B); //求四个交点
tmp=fabs((v[2]-v[1])*(v[1]-v[0]));
if (flg==0 || tmp<ans) //更新答案
{
ans=tmp; flg=1;
rep(j,0,3) w[j]=v[j];
}
}
}
db spc(db x)
{
if (fabs(x)<eps) return 0.0;
return x;
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("rectangle.in","r",stdin);
freopen("rectangle.out","w",stdout);
#endif
scanf("%d",&n);
rep(i,1,n) scanf("%lf%lf",&a[i].x,&a[i].y);
Convex(); //求凸包
solve(); //算答案
rep(i,0,3) w[4+i]=w[i];
wh=0; rep(i,1,3)
if ((sgn(w[i].y,w[wh].y)==-1) || (sgn(w[i].y,w[wh].y)==0 && sgn(w[i].x,w[wh].x)==-1)) wh=i;
printf("%.5lf\n",spc(ans));
rep(i,0,3) printf("%.5lf %.5lf\n",spc(w[wh+i].x),spc(w[wh+i].y));
return 0;
}