[P3187][HNOI2007]最小矩形覆盖(旋转卡壳)

用旋转卡壳做。先求出凸包,枚举每一条边作为矩形的一边。画画图可以看出另外三个点的决策应该是单调的。对面的点用叉乘看三角形面积大小判断,两边的点用点乘看向量在已固定的边上投影的长度(即矩形的宽)判断。注意第一次的两侧定点应分别从对面定点的两侧开始判断,需要赋初值。然后用点到直线距离,两直线交点等数学运算求一求就好了。具体看代码注释吧。最后需要避免因精度问题输出-0.00000,所以整体加一个eps。

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=50010;
const double eps=0.0000001;
struct node{
	double x,y;
}p1[N],ans[5];
double xx,yy,mn,a1,b1,c1,a2,b2,c2,a3,b3,c3,a4,b4,c4,h1,h2,hb,hc;
int n,top,sta[N],s,aa,bb,cc;
inline double cross(node a,node b,node c){
	return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);
}
inline double dot(node a,node b,node c){
	return (b.x-a.x)*(c.x-a.x)+(b.y-a.y)*(c.y-a.y);
}
inline double dist(node p,double a,double b,double c){
	double ans1=a*p.x+b*p.y+c;if(ans1<0)ans1=-ans1;
	return ans1/sqrt(a*a+b*b);
}
bool cmp(node i,node j){
	if(atan2(i.y-yy,i.x-xx)!=atan2(j.y-yy,j.x-xx))return atan2(i.y-yy,i.x-xx)<atan2(j.y-yy,j.x-xx);
	return i.x<j.x;
}
inline void getpt(double ax,double bx,double cx,double ay,double by,double cy,node& p){
	p.x=(cy*bx-cx*by)/(ax*by-ay*bx);(bx==0)?p.y=-ay/by*p.x-cy/by:p.y=-ax/bx*p.x-cx/bx;
}
int main(){
	scanf("%d",&n);
	yy=xx=1e16;
	for(int i=1;i<=n;++i){
		scanf("%lf%lf",&p1[i].x,&p1[i].y);
		if(p1[i].y<yy||(p1[i].y==yy&&p1[i].x<xx))xx=p1[i].x,yy=p1[i].y;
	}
	sort(p1+1,p1+n+1,cmp);
	top=1,sta[1]=1;
	for(int i=2;i<=n;++i){
		while(top>1&&cross(p1[sta[top-1]],p1[sta[top]],p1[i])<=0)--top;
		sta[++top]=i;
	}
	if(top<3){
		printf("0.00000\n");
		if(top==1)for(int i=1;i<=4;++i)printf("%.5lf %.5lf\n",p1[sta[1]].x,p1[sta[1]].y);
		else{
			for(int i=1;i<=2;++i)printf("%.5lf %.5lf\n",p1[sta[1]].x,p1[sta[1]].y);
			for(int i=1;i<+2;++i)printf("%.5lf %.5lf\n",p1[sta[2]].x,p1[sta[2]].y);
		}
		return 0;
	}
	sta[top+1]=sta[1],aa=bb=cc=2,mn=1e16;
	for(int i=1;i<=top;++i){
		while(cross(p1[sta[i+1]],p1[sta[i]],p1[sta[aa+1]])<=cross(p1[sta[i+1]],p1[sta[i]],p1[sta[aa]]))aa=aa%top+1;
		if(i==1)bb=aa;
		while(dot(p1[sta[i]],p1[sta[i+1]],p1[sta[bb+1]])<=dot(p1[sta[i]],p1[sta[i+1]],p1[sta[bb]]))bb=bb%top+1;
		while(dot(p1[sta[i+1]],p1[sta[i]],p1[sta[cc+1]])<=dot(p1[sta[i+1]],p1[sta[i]],p1[sta[cc]]))cc=cc%top+1;
		//求a到直线i,i+1的距离h1 
		a1=p1[sta[i+1]].y-p1[sta[i]].y,b1=p1[sta[i]].x-p1[sta[i+1]].x,
		c1=p1[sta[i+1]].x*p1[sta[i]].y-p1[sta[i+1]].y*p1[sta[i]].x;
		h1=dist(p1[sta[aa]],a1,b1,c1);
		//构建a到i,i+1的垂线
		a2=p1[sta[i]].x-p1[sta[i+1]].x,b2=p1[sta[i]].y-p1[sta[i+1]].y,
		c2=p1[sta[aa]].y*(p1[sta[i+1]].y-p1[sta[i]].y)-p1[sta[aa]].x*(p1[sta[i]].x-p1[sta[i+1]].x);
		//求b、c到a的垂线的距离之和hb+hc=h2 
		hb=dist(p1[sta[bb]],a2,b2,c2),hc=dist(p1[sta[cc]],a2,b2,c2),h2=hb+hc;
		//计算面积
		if(h1*h2<mn){
			mn=h1*h2;
			//构建过a的平行线
			a2=p1[sta[i+1]].y-p1[sta[i]].y,b2=p1[sta[i]].x-p1[sta[i+1]].x,
			c2=(p1[sta[i+1]].x-p1[sta[i]].x)*p1[sta[aa]].y-(p1[sta[i+1]].y-p1[sta[i]].y)*p1[sta[aa]].x;
			//构建b、c到i,i+1的垂线 
			a3=p1[sta[i]].x-p1[sta[i+1]].x,b3=p1[sta[i]].y-p1[sta[i+1]].y,
			c3=p1[sta[bb]].y*(p1[sta[i+1]].y-p1[sta[i]].y)-p1[sta[bb]].x*(p1[sta[i]].x-p1[sta[i+1]].x);
			a4=p1[sta[i]].x-p1[sta[i+1]].x,b4=p1[sta[i]].y-p1[sta[i+1]].y,
			c4=p1[sta[cc]].y*(p1[sta[i+1]].y-p1[sta[i]].y)-p1[sta[cc]].x*(p1[sta[i]].x-p1[sta[i+1]].x);
			//计算交点,保存到ans数组 
			getpt(a1,b1,c1,a3,b3,c3,ans[0]),getpt(a1,b1,c1,a4,b4,c4,ans[1]),
			getpt(a2,b2,c2,a4,b4,c4,ans[2]),getpt(a2,b2,c2,a3,b3,c3,ans[3]);
		}
	}
	printf("%.5lf\n",mn);
	s=0;
	for(int i=1;i<=3;++i)if(ans[i].y<ans[s].y||(ans[i].y==ans[s].y&&ans[i].x<ans[s].x))s=i;
	for(int i=0;i<4;++i)printf("%.5lf %.5lf\n",ans[(s+i)%4].x+eps,ans[(s+i)%4].y+eps); 
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值