HDU 5251

这题老早就想A了,就因为这个才想学旋转卡壳,但是折磨了我好久,今天终于过了哈哈哈

凸包及旋转卡壳请参考我的这篇博客

https://mp.csdn.net/mp_blog/creation/editor/120031148

这题跟模板不同的是题目要求输出整数,而且是四舍五入的!一开始没注意到吃了大亏,不过不吃那亏也注意不到我模板写错了……因祸得福哈哈

模板和原理都在上篇博客中讲述的很清楚了,下面是模板的运用

AC代码

#include<cstdio> //计算几何基础 代码 https://blog.csdn.net/qq_44691917/article/details/104686146 
#include<iostream>//理论https://www.luogu.com.cn/blog/wjyyy/geometry1 
#include<cmath>//自己写的总结 https://mp.csdn.net/mp_blog/creation/editor/120031148
#include<vector>//可能的坑:多边形不能是一条线!  如果题目可能有这种坑,请提前判断多边形的所有点是否共线 
#include<algorithm>
using namespace std;
#define Vector Point//向量在数学意义上是矢量,但是计算机并不能存储方向,因此还是要用坐标表示,但是因为一些函数是基于向量计算的,与点没有关系,为了区别开我们重定义向量名Vector
#define Segment Line//可以用两个点表示线段,起点是p1,终点是p2。直接用直线的数据结构定义线段
//typedef vector<Point> Polygon;//用点集描绘多边形  在134行定义,现在头文件标注一下
const double PI=acos(-1.0);
const double eps=1e-8;
/* 目录  10~88行为点和向量专题  90-131行后为点和线专题 133-159为多边专题 161后为凸包-旋转卡壳专题*/

inline int sgn(double d) {  //判断浮点数符号 正数返回1 0返回0 负数返回-1
	if(fabs(d)<eps) return 0;
	return d>0?1:-1;
}

inline int cmp(double x,double y) { //浮点数比较大小 x>y返回1,x=y返回0,x<y返回-1
	return sgn(x-y);
}

struct Point {//点Point 向量Vector共用同一个数据结构
	double x,y;
	Point(double a=0.0,double b=0.0):x(a),y(b) {}//结构体初始化 https://blog.csdn.net/qq_54886579/article/details/120024152

	Vector operator + (Vector B) {//重载运算符
		return Vector(x+B.x,y+B.y);
	}

	Vector operator - (Point B) {//两个点作差将得到一个向量。向量之间的减法“共起点,指被减”,这里的起点在二维坐标系中指原点。向量A-B得到BA
		return Vector(x-B.x,y-B.y);
	}

	Vector operator * (double d) { //数乘 注意double要放在*后面 不然报错
		return Vector(x*d,y*d);
	}

	double operator * (Vector B) { //数量积
		return x*B.x+y*B.y;
	}

	Vector operator / (double d) {//向量与实数相除得到等比例缩放的向量
		return Vector(x/d,y/d);
	}

	double operator ^ (Vector B) { //叉乘 用^表示 等价于空间坐标系z轴为0的叉乘
		return x*B.y-y*B.x;
	}

	bool operator < (const Point &b) const {//用于对点集排序 先比x再比y
		if(sgn(x-b.x)==0) return y<b.y;
		return x<b.x;
	}

	bool operator == (const Point& b) const {
		if(sgn(x-b.x)==0 && sgn(y-b.y)==0)
			return true;
		return false;
	}
};

double norm(Vector A) {  //模长
	return sqrt(A*A);
}

double sqrnorm(Vector A) { //模长平方
	return A*A;
}

double angle(Vector A,Vector B) { //向量夹角
	return acos(A*B/norm(A)/norm(B));
}

Vector rotate(Vector A,double rad) { //逆时针旋转 x'=xcosα-ysinα y'=xsinα+ycosα
	return Vector(A.x*cos(rad)-A.y*sin(rad), A.x*sin(rad)+A.y*cos(rad));
}

Vector normal(Vector A) { //计算向量逆时针旋转九十度(即单位法向量) x'=-y y'=x 然后再除以模长即可
	double L=norm(A);
	return Vector(-A.y/L, A.x/L);
}

bool ToLeftTest(Point c, Point a, Point b) { //逆时针方向的边 a为起点 b为终点 c为边外一点 判断c是否在ab的左边 ab^ac
	return ((b-a)^(c-a))>0;
}

double Distance(Point a,Point b) { //点a到点b的距离
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

struct Line { //直线的表示:线上两个点 Segment线段和直线Line通过typedef共用同一个数据结构
	Point p1,p2;
	Line(Point a,Point b):p1(a),p2(b) {}
};

//点和直线的位置关系 点在直线左边返回1 右边返回2 在直线上返回0
int PointLineRelation(Point p,Line v) { //在二维平面上有3种,即点在直线左侧,在右侧,在直线上。用直线上两点p1,p2与点p构成两个向量,用叉积的正负判断方向,就能得到位置关系。
	int c=sgn((p-v.p1)^(v.p2-v.p1));//向量p1p 叉乘 p1p2
	if(c<0) return 1;
	if(c>0) return 2;
	return 0;
}
//点和线段的位置关系  0为不在线段上,1为在线段上
bool PointOnSeg(Point p,Segment v) { //判断点p是否在线段v上,先用叉积判断是否共线,然后用点积看p和v的两个端点产生的角是否为钝角。
	return sgn((p-v.p1)^(v.p2-v.p1)) ==0 && sgn((p-v.p1)*(p-v.p2))<=0;
}
//点到直线的距离 已知点p和直线v(p1,p2),求p到v的距离。首先用叉积求p、p1、p2构成的平行四边形的面积 然后用面积除以平行四边形的底边长,也就是线段(p1,p2)的长度,就得到了平行四边形的高,即p点到直线的距离。
double DisPointLine(Point p,Line v) {
	return fabs((p-v.p1)^(v.p2-v.p1))/Distance(v.p1,v.p2);
}

int LineRelation(Line v1,Line v2) { //两条直线的位置关系 用两条直线方向向量叉乘来判断 等于0则判断平行还是重合 否则相交 0 平行  1 重合 2 相交
	if(sgn((v1.p2-v1.p1)^(v2.p2-v2.p1))==0) {
		if(PointLineRelation(v1.p1,v2)==0) return 1;//1 重合
		else return 0;//0 平行
	} else return 2; //2 相交
}

bool CrossSegment(Line l1,Line l2) { //判断两直线是否相交
	Point a=l1.p1,b=l1.p2,c=l2.p1,d=l2.p2;//如果一条线段的两端在另一条线段的两侧,那么两个端点与另一线段产生的两个叉积正负相反
	double c1=(b-a)^(c-a),c2=(b-a)^(d-a);//也就是说两个叉积相乘为负。如果两条线段互相满足这一点,那么就是相交的
	double d1=(d-c)^(a-c),d2=(d-c)^(b-c);//c1=AB^AC c2=AB^AD d1=CD^CA d2=CD^CB
	return sgn(c1)*sgn(c2)<0 && sgn(d1)*sgn(d2)<0;
}

Point CrossPoint(Line l1,Line l2) { //两直线交点(叉乘公式) 解释见代码下方注释区  注意两直线不能重合,否则会输出nan,可以用isnan来进行判断和维护鲁棒性
	Point a=l1.p1,b=l1.p2,c=l2.p1,d=l2.p2;
	double s1=(d-a)^(c-a),s2=(c-b)^(d-b),s=s1/(s1+s2);
	Point t=b-a;
	return a+t*s;
}

typedef vector<Point> Polygon;

int Point_In_Polygon(Point pt,Polygon p) { //判断点Pt与多边形的位置关系  0外部 1内部 2边上 3顶点上
	int n=p.size();
	for(int i=0; i<n; i++)	if(p[i]==pt)	return 3; //点在顶点上
	for(int i=0; i<n; i++) {
		Line v=Line(p[i],p[(i+1)%n]);
		if(PointOnSeg(pt,v)) return 2;//点在边上
	}
	int cnt=0;
	for(int i=0; i<n; i++) {
		int j=(i+1)%n;//i为起点 j为终点
		bool c=ToLeftTest(pt,p[i],p[j]);
		int u=sgn(p[i].y-pt.y),v=sgn(p[j].y-pt.y);
		if(c && u<0 && v>=0)	cnt++;
		if(!c && u>=0 && v<0)	cnt--;
	}
	return cnt!=0;//1 内部 0 外部
}

double Polygon_Area(Polygon p) { //求多边形面积,n为多边形顶点数
	double area=0;
	int n=p.size();
	for(int i=0; i<n; i++) area+=(p[i]^p[(i+1)%n]);
	return fabs(area/2);
}

Polygon Andrew(Polygon p) {//返回凸包 时间复杂度O(nlogn) 多边形不能是一条线!
	int n=p.size();
	sort(p.begin(),p.end());
	Polygon ch;//凸包结果
	for(int i=0; i<n; i++) {  	//起点不能出队  所以size>1
		while(ch.size()>1 && !ToLeftTest(p[i],ch[ch.size()-2],ch[ch.size()-1])) { //下凸包 如果上一条边到这一条边不符合左旋要求,上一个点出队
//			printf("update: segment %d TO %d destination: %d\n",ch.size()-2,ch.size()-1,i);
			ch.pop_back();
		}
		ch.push_back(p[i]);//这一个点入队
//		printf("ch[ %d ]= %d\n",ch.size()-1,i);
	}
	int k=ch.size();
	for(int i=n-2; i>=0; i--) { //从p的倒数第二个点开始,因为下凸包终点是倒数第一个点,倒数第一个点就是上凸包的起点
		while(ch.size()>k && !ToLeftTest(p[i],ch[ch.size()-2],ch[ch.size()-1])) {         //上凸包  如果不符合左旋要求,上一个点出队
//			printf("update: segment %d TO %d destination: %d\n",ch.size()-2,ch.size()-1,i);
			ch.pop_back();
		}
		ch.push_back(p[i]);
//		printf("ch[ %d ]= %d\n",ch.size()-1,i);
	}
	if(n>1) ch.pop_back();//起点被重复加入,删去
	return ch;
}

//旋转卡壳专题 多边形不能是一条线!
double diameter(Polygon p) { //求凸多边形p的直径(多边形上任意两点间的距离的最大值) 时间复杂度O(n) 注意,p是凸多边形,所以要先求出凸包
	int n=p.size(),t=2%n;//易错 n不要设为size-1,否则取模后会漏掉0 比如size=2 n=2 那么t=0 1 2 1 2 漏掉了0
	double ans=0.0;
	for(int i=0; i<n; i++) {
		Line l=Line(p[i],p[(i+1)%n]);//当前边
		while(DisPointLine(p[t],l)<=DisPointLine(p[(t+1)%n],l)) { //求使当前边到某点的距离最大的点
			t=(t+1)%n;
		}
		ans=max(ans,max(Distance(p[i],p[t]),Distance(p[(i+1)%n],p[t])));
	}
	return ans;
}

double convexwidth(Polygon p) { //求凸多边形p的宽度(平行切线间的最小距离) 时间复杂度O(n)
	int n=p.size(),t=2%n;//平行线间距可以转化为平行线上一点到线的距离,即点线距
	double ans=0.0;//由凸包的性质(没有凹边),我们可以以某边为基础遍历点,距离最大的点一定为凸包的切线,这个点到边的距离即为平行切线间距
	for(int i=0; i<n; i++) {
		Line l=Line(p[i],p[(i+1)%n]);//当前边
		while(DisPointLine(p[t],l)<=DisPointLine(p[(t+1)%n],l)) { //求使当前边到某点的距离最大的点
			t=(t+1)%n;
		}
		ans=max(ans,DisPointLine(p[t],l));
	}
	return ans;
}

Point project(Point p, Segment s) {//求点p在直线s上的投影
	Vector base = s.p2 - s.p1;
	double r = ( (p - s.p1)* base) / sqrnorm(base);
	base = base * r;
	return Point(s.p1 + base);
}

double recArea(Polygon ch) {//最小外接矩形面积 时间复杂度O(N)
	int r=2,q=1,p,num=ch.size();//r为底边的对边切点,q为垂直于底边方向最远点,表现为正投影,p为另一侧最远点,表现为负投影
	double res=1e10;//易错:q从1开始,因为类似三角形,最大投影就在底边!如果q=2,由于单峰,不可能再取到最大正投影了,同理r不能取0,因为是>而不是>=,r=0或1高为0
	for(int i=0; i<num; i++) {
		Line l=Line(ch[i],ch[(i+1)%num]);
		while(sgn(DisPointLine(ch[(r+1)%num],l)-DisPointLine(ch[r],l))>0) r=(r+1)%num;//寻找最大的高		
		//i->i+1 点乘 i->q  求投影*底边长 求最大正投影 * 底边长
		while(sgn( ((ch[(i+1)%num]-ch[i]) *(ch[(q+1)%num]-ch[i])) - ((ch[(i+1)%num]-ch[i])*(ch[q]-ch[i])) )>0 )  q=(q+1)%num;//寻找投影极值,卡矩形的宽
		if(i==0) p=q+1;//凸包投影是单峰函数,递增后必递减或相等(连续等值点最长为2),所以从此开始搜索 +1是为了防止垂直于l的边的两个点
		//i->i+1 点乘 i->p  求投影*底边长 求最大负投影 * 底边长
		while(sgn( ((ch[(i+1)%num]-ch[i]) *(ch[(p+1)%num]-ch[i])) - ((ch[(i+1)%num]-ch[i])*(ch[p]-ch[i])) )<=0 ) {
			p=(p+1)%num;//小于等于为了防止垂直线的两个点
		}
		double h=DisPointLine(ch[r],l);//以当前边为矩形的高
		double w=( ((ch[(i+1)%num]-ch[i])*(ch[q]-ch[i])) - ((ch[(i+1)%num]-ch[i])*(ch[p]-ch[i])) )/Distance(ch[i],ch[(i+1)%num]);//正投影减负投影 得到宽
		res=min(res,h*w);
	}
	return res;
}

int main() {
	int T;//多边形点的个数
	scanf("%d",&T);
	for(int q=1;q<=T;q++){
		int N;
		scanf("%d",&N);
		Polygon p,ch;//p为多边形,ch为凸包
		Point pt;
		for(int i=1; i<=N; i++) {
			for(int j=1; j<=4; j++) {
				scanf("%lf%lf",&pt.x,&pt.y),p.push_back(pt);	
			}
		}
		ch=Andrew(p);
		int recarea=round(recArea(ch));
		printf("Case #%d:\n",q); 
		cout<<recarea<<endl;
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值