这题老早就想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;
}