P3187 最小矩形覆盖 题解
补充重要性质的证明,以及给出目前最优解第一的代码实现(完全没有刻意卡常)(带注释)。
大部分内容题解区已有题解已经讲的十分清楚了,但是没有对于“最优情况下至少有一条矩形边与凸包的边重合”的证明,我在写这道题的时候感到比较困惑且题解区没有给出证明,解决这个困惑后决定发题解补充:
首先矩形的边必定与凸包的点相交,否则可以将边向凸包平移调整直到与第一个凸包上的点相交,仍然满足所有点都在矩形内且答案变小。将凸包落在矩形对边的点连接(如下图),后续见下图。
发现自己的代码目前是最优解第一,所以放一下代码实现以供参考(有注释):
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
const double eps=1e-10;
int n;
inline bool same(double x,double y) { return fabs(x-y)<eps;}
struct Point {
double x,y;
inline bool operator<(const Point &t)const {
if(!same(x,t.x)) return x<t.x;
return y<t.y;
}
inline Point operator+(const Point &t)const {return {x+t.x,y+t.y};}
inline Point operator-(const Point &t)const {return {x-t.x,y-t.y};}
inline Point operator*(const double &t)const {return {x*t,y*t};}//数乘
inline double operator*(const Point &t)const {return x*t.y-y*t.x;} //叉积
inline double operator^(const Point &t)const {return x*t.x+y*t.y;} //点积
}p[N],tmp[N];
struct Line { Point s,v;};//点向式结构体
inline Point inter(Line l1,Line l2) {
Point t1=l1.s+l1.v;
double s1=l2.v*(l1.s-l2.s);
double s2=(t1-l2.s)*l2.v;
double t=s1/(s1+s2);
return l1.s+l1.v*t;
} //求直线交点
double ans=1e18;
Point a[4]; //答案矩形4个顶点
inline Point get(Point p) {
p.x=(fabs(p.x)<eps)?0:p.x;
p.y=(fabs(p.y)<eps)?0:p.y;
return p;
} //=0可能会输出0/-0 所以要判
inline void update(Line l1,Line l2,Line l3,Line l4) {
Point b[4]={inter(l1,l2),inter(l2,l3),inter(l3,l4),inter(l4,l1)};
double now=fabs(b[0]*b[1]+b[1]*b[2]+b[2]*b[3]+b[3]*b[0])/2;
if(ans>now) {
ans=now;
for(int i=0;i<4;i++) a[i]=get(b[i]);
}
} // 求4条直线围成的4边形面积 更新答案 注意直线顺序
inline Point rotate(Point p) { return {-p.y,p.x};} // 把p旋转90度
inline void getline(Line l1,Point p2,Point p3,Point p4) {
Point _v=rotate(l1.v);
Line l2={p2,_v},l3={p3,l1.v},l4={p4,_v};
update(l1,l2,l3,l4);
} //确定一条直线和另外3个点 求出围成矩形的4条直线并更新答案
int stk[N],top;
void tb() {
sort(p+1,p+n+1);
for(int i=1;i<=n;i++) {
while(top>1&&((p[i]-p[stk[top-1]])*(p[stk[top]]-p[stk[top-1]]))<eps) top--;
stk[++top]=i;
}
int limit=top;
for(int i=n;i>=1;i--) {
while(top>limit&&((p[i]-p[stk[top-1]])*(p[stk[top]]-p[stk[top-1]]))<eps) top--;
stk[++top]=i;
}
for(int i=1;i<=top;i++) tmp[i]=p[stk[i]];
for(int i=1;i<=top;i++) p[i]=tmp[i];
n=top-1;
}
inline double val(Point a,Point b,Point c) {
return fabs((a-b)*(a-c));
} //三角形面积
void work() {
for(int i=1,j=2,x=2,y=2;i<=n;i++) { //i x j y
while(val(p[i],p[i+1],p[j])<val(p[i],p[i+1],p[j%n+1])) j=j%n+1;
y=max(y,j);
while(x!=j&&((p[i+1]-p[i])^(p[x+1]-p[x]))>-eps) x=x%n+1; //>=0 (锐角/直角) <=> >-eps
while(y!=i&&((p[i+1]-p[i])^(p[y+1]-p[y]))<eps) y=y%n+1;
getline(Line{p[i],p[i+1]-p[i]},p[x],p[j],p[y]);
}
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
tb();
work();
printf("%.5lf\n",ans);
reverse(a,a+4);// a 本身是顺时针的
int pos=0;
for(int i=1;i<4;i++)
if(a[pos].y>a[i].y)
pos=i;
for(int i=pos;i<4;i++) printf("%.5lf %.5lf\n",a[i].x,a[i].y);
for(int i=0;i<pos;i++) printf("%.5lf %.5lf\n",a[i].x,a[i].y);
return 0;
}