[好题][旋转卡壳]最小矩形覆盖 AcWing2142

已知平面上不共线的一组点的坐标,求覆盖这组点的面积最小的矩形。

输出矩形的面积和四个顶点的坐标。

输入格式

第一行包含一个整数 n,表示点的数量。

接下来 n 行,每行包含两个用空格隔开的浮点数,表示一个点的 x 坐标和 y 坐标。

不用科学计数法,但如果小数部分为 0,则可以写成整数。

输出格式

共 5 行,第 1 行输出一个浮点数,表示所求得的覆盖输入点集的最小矩形的面积。

接下来 4 行,每行包含两个用空格隔开的浮点数,表示所求矩形的一个顶点的 x 坐标和 y 坐标。

先输出 y 坐标最小的顶点的 x,y 坐标,如果有两个点的 y 坐标同时达到最小,则先输出 x 坐标较小者的 x,y 坐标。

然后,按照逆时针的顺序输出其他三个顶点的坐标。

不用科学计数法,精确到小数点后 5 位,后面的 0 不可省略。

答案不唯一,输出任意一组正确结果即可。

数据范围

3≤n≤50000

输入样例:

6
1.0 3.00000
1 4.00000
2.00000 1
3 0.00000
3.00000 6
6.0 3.0

输出样例:

18.00000
3.00000 0.00000
6.00000 3.00000
3.00000 6.00000
0.00000 3.00000

题意: 题意很简单,就是找到一个面积最小的矩形,使其能够包围平面上的全部点。

分析: 有一个结论,满足题意的矩形一定有一条边和凸包上一边重合。那么就可以枚举凸包的边,然后旋转卡壳找到距离该边最远的点,这时候矩形的宽就知道了。对于矩形的长同样用到旋转卡壳,卡出最左和最右的点,不过这里的旋转卡壳条件要改变一下,考虑到点乘的几何意义这里应该把叉乘换成点乘,这样最左和最右的点才具有单调性。

最后求出矩形上四个点后就可以表示出四条边,分别求交点就是矩形四个顶点。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const double eps = 1e-8;
const double inf = 1e20;
const double pi = acos(-1.0);
const int maxp = 100010;
//`Compares a double to zero`
int sgn(double x)
{
	if(fabs(x) < eps)return 0;
	if(x < 0)return -1;
	else return 1;
}
//square of a double
inline double sqr(double x){return x*x;}

struct Point
{
	double x, y;
	Point(){}
	Point(double _x,double _y){x = _x, y = _y;}
	void input(){scanf("%lf%lf",&x,&y);}
	void output(){printf("%.2f %.2f\n",x,y);}
	bool operator == (Point b)const{return sgn(x-b.x) == 0 && sgn(y-b.y) == 0;}
	bool operator < (Point b)const{return sgn(y-b.y)== 0?sgn(x-b.x)<0:y<b.y;}
	Point operator -(const Point &b)const{return Point(x-b.x,y-b.y);}
	//叉积
	double operator ^(const Point &b)const{return x*b.y - y*b.x;}
	//点积
	double operator *(const Point &b)const{return x*b.x + y*b.y;}
	//返回长度
	double len(){return hypot(x,y);/*库函数*/}
	//返回长度的平方
	double len2(){return x*x + y*y;}
	//返回两点的距离
	double distance(Point p){return hypot(x-p.x,y-p.y);}
	Point operator +(const Point &b)const{return Point(x+b.x,y+b.y);}
	Point operator *(const double &k)const{return Point(x*k,y*k);}
	Point operator /(const double &k)const{return Point(x/k,y/k);}
}; 

struct Line
{
	Point s,e;
	Line(){}
	Line(Point _s,Point _e){s = _s, e = _e;}
	bool operator ==(Line v){return (s == v.s)&&(e == v.e);}
	//`根据一个点和倾斜角angle确定直线,0<=angle<pi`
	Line(Point p,double angle)
	{
		s = p;
		if(sgn(angle-pi/2) == 0){e = (s + Point(0,1));}
		else{e = (s + Point(1,tan(angle)));}
	}
	//ax+by+c=0
	Line(double a,double b,double c)
	{
		if(sgn(a) == 0)	s = Point(0,-c/b), e = Point(1,-c/b);
		else if(sgn(b) == 0) s = Point(-c/a,0), e = Point(-c/a,1);
		else s = Point(0,-c/b), e = Point(1,(-c-a)/b);
	}
	void input()
	{
		s.input();
		e.input();
	}
	void adjust(){if(e < s)swap(s,e);}
	//求线段长度
	double length(){return s.distance(e);}
	//`返回直线倾斜角 0<=angle<pi`
	double angle()
	{
		double k = atan2(e.y-s.y,e.x-s.x);
		if(sgn(k) < 0)k += pi;
		if(sgn(k-pi) == 0)k -= pi;
		return k;
	}
	//`点和直线关系`
	//`1  在左侧`
	//`2  在右侧`
	//`3  在直线上`
	int relation(Point p)
	{
		int c = sgn((p-s)^(e-s));
		if(c < 0)return 1;
		else if(c > 0)return 2;
		else return 3;
	}
	// 点在线段上的判断
	bool pointonseg(Point p){return sgn((p-s)^(e-s)) == 0 && sgn((p-s)*(p-e)) <= 0;}
	//`两向量平行(对应直线平行或重合)`
	bool parallel(Line v){return sgn((e-s)^(v.e-v.s)) == 0;/*两向量叉积为0*/ }
	//`两线段相交判断`
	//`2 规范相交`
	//`1 非规范相交`
	//`0 不相交`
	int segcrossseg(Line v)
	{
		int d1 = sgn((e-s)^(v.s-s));
		int d2 = sgn((e-s)^(v.e-s));
		int d3 = sgn((v.e-v.s)^(s-v.s));
		int d4 = sgn((v.e-v.s)^(e-v.s));
		if( (d1^d2)==-2 && (d3^d4)==-2 )return 2;//如果线段两端点在另一条线段两侧 
		return (d1==0 && sgn((v.s-s)*(v.s-e))<=0) ||//枚举交点 
			(d2==0 && sgn((v.e-s)*(v.e-e))<=0) ||
			(d3==0 && sgn((s-v.s)*(s-v.e))<=0) ||
			(d4==0 && sgn((e-v.s)*(e-v.e))<=0);
	}
	//`直线和线段相交判断`
	//`-*this line   -v seg`
	//`2 规范相交`
	//`1 非规范相交`
	//`0 不相交`
	int linecrossseg(Line v)
	{
		int d1 = sgn((e-s)^(v.s-s));
		int d2 = sgn((e-s)^(v.e-s));
		if((d1^d2)==-2) return 2;
		return (d1==0||d2==0);
	}
	//`两直线关系`
	//`0 平行`
	//`1 重合`
	//`2 相交`
	int linecrossline(Line v)
	{
		if((*this).parallel(v))//此时平行或者重合 
			return v.relation(s)==3;//如果当前直线起点在另一条直线上 
		return 2;
	}
	//`求两直线的交点`
	//`要保证两直线不平行或重合`
	Point crosspoint(Line v)//同底三角形面积比 
	{
		double a1 = (v.e-v.s)^(s-v.s);
		double a2 = (v.e-v.s)^(e-v.s);
		return Point((s.x*a2-e.x*a1)/(a2-a1),(s.y*a2-e.y*a1)/(a2-a1));
	}
	//点到直线的距离
	double dispointtoline(Point p){return fabs((p-s)^(e-s))/length();}
	//点到线段的距离
	double dispointtoseg(Point p)
	{
		if(sgn((p-s)*(e-s))<0 || sgn((p-e)*(s-e))<0)
			return min(p.distance(s),p.distance(e));
		return dispointtoline(p);
	}
	//`返回线段到线段的距离`
	//`前提是两线段不相交,相交距离就是0了`
	double dissegtoseg(Line v){return min(min(dispointtoseg(v.s),dispointtoseg(v.e)),min(v.dispointtoseg(s),v.dispointtoseg(e)));}
	//`返回点p在直线上的投影`利用点乘的定义 
	Point lineprog(Point p){return s + ( ((e-s)*((e-s)*(p-s)))/((e-s).len2()) );}
	//`返回点p关于直线的对称点`
	Point symmetrypoint(Point p)
	{
		Point q = lineprog(p);
		return Point(2*q.x-p.x,2*q.y-p.y);
	}
};

struct polygon
{
	int n;
	Point p[maxp];
	Line l[maxp];
	void input(int _n){
		n = _n;
		for(int i = 0;i < n;i++)
			p[i].input();
	}
	void add(Point q){
		p[n++] = q;
	}
	void getline(){
		for(int i = 0;i < n;i++){
			l[i] = Line(p[i],p[(i+1)%n]);
		}
	}
	struct cmp{
		Point p;
		cmp(const Point &p0){p = p0;}
		bool operator()(const Point &aa,const Point &bb){
			Point a = aa, b = bb;
			int d = sgn((a-p)^(b-p));
			if(d == 0){
				return sgn(a.distance(p)-b.distance(p)) < 0;//极角相同,距离原点近的靠前 
			}
			return d > 0;
		}
	};
	//`进行极角排序`
	//`首先需要找到最左下角的点`
	//`需要重载号好Point的 < 操作符(min函数要用) `
	void norm(){
		Point mi = p[0];
		for(int i = 1;i < n;i++)mi = min(mi,p[i]);
		sort(p,p+n,cmp(mi));
	}
	//`得到凸包`
	//`得到的凸包里面的点编号是0$\sim$n-1的`
	//`两种凸包的方法`
	//`注意如果有影响,要特判下所有点共点,或者共线的特殊情况`
	//`测试 LightOJ1203  LightOJ1239`
	void getconvex(polygon &convex){
		sort(p,p+n);
		convex.n = n;
		for(int i = 0;i < min(n,2);i++){
			convex.p[i] = p[i];
		}
		if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
		if(n <= 2)return;
		int &top = convex.n;
		top = 1;
		for(int i = 2;i < n;i++){
			while(top && sgn((convex.p[top]-p[i])^(convex.p[top-1]-p[i])) <= 0)
				top--;
			convex.p[++top] = p[i];
		}
		int temp = top;
		convex.p[++top] = p[n-2];
		for(int i = n-3;i >= 0;i--){
			while(top != temp && sgn((convex.p[top]-p[i])^(convex.p[top-1]-p[i])) <= 0)
				top--;
			convex.p[++top] = p[i];
		}
		if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
		convex.norm();//`原来得到的是顺时针的点,排序后逆时针`
	}
	//`得到凸包的另外一种方法`
	//`测试 LightOJ1203  LightOJ1239`
	//如果要保留所有共线点需要极角排序后最后一条边上的点逆序处理且出栈条件去掉等号
	//去掉等号后需要保证不存在重复点,否则会出错 
	//更改一下最后一条边的排序顺序 
	//	int k;
	//	for(k = a.n-1; k >= 0; k--)
	//		if(sgn((a.p[k]-a.p[0])^(a.p[k-1]-a.p[0])) != 0)
	//			break; 
	//	reverse(a.p+k, a.p+a.n); 
	void Graham(polygon &convex){
		norm();
		int &top = convex.n;
		top = 0;
		if(n == 1){
			top = 1;
			convex.p[0] = p[0];
			return;
		}
		if(n == 2){
			top = 2;
			convex.p[0] = p[0];
			convex.p[1] = p[1];
			if(convex.p[0] == convex.p[1])top--;
			return;
		}
		convex.p[0] = p[0];
		convex.p[1] = p[1];
		top = 2;
		for(int i = 2;i < n;i++){
			while( top > 1 && sgn((convex.p[top-1]-convex.p[top-2])^(p[i]-convex.p[top-2])) <= 0 )
				top--;
			convex.p[top++] = p[i];
		}
		if(convex.n == 2 && (convex.p[0] == convex.p[1]))convex.n--;//特判
	}
	//`判断是不是凸的`
	bool isconvex(){
		bool s[3];
		memset(s,false,sizeof(s));
		for(int i = 0;i < n;i++){
			int j = (i+1)%n;
			int k = (j+1)%n;
			s[sgn((p[j]-p[i])^(p[k]-p[i]))+1] = true;
			if(s[0] && s[2])return false;
		}
		return true;
	}
	//`得到周长`,点要相邻 
	//`测试 LightOJ1239`
	double getcircumference(){
		double sum = 0;
		for(int i = 0;i < n;i++)
			sum += p[i].distance(p[(i+1)%n]);
		return sum;
	}
	//`得到面积`,点要相邻 
	double getarea()
	{
		double sum = 0;
		for(int i = 0;i < n;i++)
			sum += (p[i]^p[(i+1)%n]);
		return fabs(sum)/2;
	}
	//`判断点和任意多边形的关系`
	//` 3 点上`
	//` 2 边上`
	//` 1 内部`
	//` 0 外部`
	int relationpoint(Point q)
	{
		for(int i = 0;i < n;i++){
			if(p[i] == q)return 3;
		}
		getline();
		for(int i = 0;i < n;i++){
			if(l[i].pointonseg(q))return 2;
		}
		int cnt = 0;
		for(int i = 0;i < n;i++){//图形学上的内外测试 
			int j = (i+1)%n;
			int k = sgn((q-p[j])^(p[i]-p[j]));//向右侧引一条射线 
			int u = sgn(p[i].y-q.y);
			int v = sgn(p[j].y-q.y);
			if(k > 0 && u < 0 && v >= 0)cnt++;//向量两点y值小的被截断一部分 
			if(k < 0 && v < 0 && u >= 0)cnt--;
		}
		return cnt != 0;
	}
	//`得到方向`
	//` 1 表示逆时针,0表示顺时针`
	bool getdir()
	{
		double sum = 0;
		for(int i = 0;i < n;i++)
			sum += (p[i]^p[(i+1)%n]);
		if(sgn(sum) > 0)return 1;
		return 0;
	}
	//`得到重心`
	Point getbarycentre()
	{
		Point ret(0,0);
		double area = 0;
		for(int i = 1;i < n-1;i++){
			double tmp = (p[i]-p[0])^(p[i+1]-p[0]);
			if(sgn(tmp) == 0)continue;
			area += tmp;
			ret.x += (p[0].x+p[i].x+p[i+1].x)/3*tmp;
			ret.y += (p[0].y+p[i].y+p[i+1].y)/3*tmp;
		}
		if(sgn(area)) ret = ret/area;
		return ret;
	}
};
//`AB X AC`
double cross(Point A,Point B,Point C){ return (B-A)^(C-A); }
//`AB*AC`
double dot(Point A,Point B,Point C){ return (B-A)*(C-A); }

//double minRectangleCover(polygon A){
//	//`要特判A.n < 3的情况`
//	if(A.n < 3)return 0.0;
//	A.p[A.n] = A.p[0];
//	double ans = -1;
//	int r = 1, p = 1, q;
//	for(int i = 0;i < A.n;i++){
//		//`卡出离边A.p[i] - A.p[i+1]最远的点`
//		while( sgn( cross(A.p[i],A.p[i+1],A.p[r+1]) - cross(A.p[i],A.p[i+1],A.p[r]) ) >= 0 )
//			r = (r+1)%A.n;
//		//`卡出A.p[i] - A.p[i+1]方向上正向n最远的点`
//		while(sgn( dot(A.p[i],A.p[i+1],A.p[p+1]) - dot(A.p[i],A.p[i+1],A.p[p]) ) >= 0 )
//			p = (p+1)%A.n;
//		if(i == 0)q = p;
//		//`卡出A.p[i] - A.p[i+1]方向上负向最远的点`
//		while(sgn(dot(A.p[i],A.p[i+1],A.p[q+1]) - dot(A.p[i],A.p[i+1],A.p[q])) <= 0)
//			q = (q+1)%A.n;
//		double d = (A.p[i] - A.p[i+1]).len2();
//		double tmp = cross(A.p[i],A.p[i+1],A.p[r]) *
//			(dot(A.p[i],A.p[i+1],A.p[p]) - dot(A.p[i],A.p[i+1],A.p[q]))/d;
//		if(ans < 0 || ans > tmp)ans = tmp;
//	}
//	return ans;
//}

polygon a, con;

signed main()
{
	int n;
	cin >> n;
	a.input(n);
	a.Graham(con);
	a = con;
	for(int i = 0; i < con.n; i++)
		a.add(con.p[i]);
	for(int i = 0; i < con.n; i++)
		a.add(con.p[i]);
	double ans = inf;
	int aa, bb, cc, dd;//四边形边界上四点 
	//枚举边 
	for(int i = 0, j = 2, k = 1, l; i < con.n; i++)
	{
		//卡出垂向最远点 
		while(cross(a.p[i], a.p[i+1], a.p[j]) < cross(a.p[i], a.p[i+1], a.p[j+1]))
			j++;
		//卡出正向最远点
		while(dot(a.p[i], a.p[i+1], a.p[k]) < dot(a.p[i], a.p[i+1], a.p[k+1]))
			k++;
		//卡出负向最远点 
		l = j;
		while(dot(a.p[i], a.p[i+1], a.p[l]) > dot(a.p[i], a.p[i+1], a.p[l+1]))
			l++;
		double len = a.p[i].distance(a.p[i+1]);
		//求出宽 
		double p = cross(a.p[i], a.p[i+1], a.p[j])/len;
		//求出长
		double q = dot(a.p[i], a.p[i+1], a.p[k])/len-dot(a.p[i], a.p[i+1], a.p[l])/len; 
		if(ans > q*p)
		{
			ans = q*p;
			aa = i;
			bb = k;
			cc = j;
			dd = l; 
		} 
	}
	Point t1 = a.p[aa+1]-a.p[aa];
	Point t2 = Point(-t1.y, t1.x);//旋转90°后的向量 
	Line l1(a.p[aa], a.p[aa+1]);
	Line l2(a.p[bb], a.p[bb]+t2); 
	Line l3(a.p[cc], a.p[cc]+t1);
	Line l4(a.p[dd], a.p[dd]+t2);
	printf("%.5f\n", ans);
	a.n = 0;
	a.add(l1.crosspoint(l2));
	a.add(l2.crosspoint(l3));
	a.add(l3.crosspoint(l4));
	a.add(l4.crosspoint(l1));
	a.norm();
	for(int i = 0; i < 4; i++)
		printf("%.5f %.5f\n", a.p[i].x+eps, a.p[i].y+eps);
	return 0;
} 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值