下面有很多没用到的函数,所以请从主函数开始看
计算几何详解请参考我的博客计算几何基础
#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-144行后为点和线专题 146-173为多边专题 175后为凸包-旋转卡壳专题*/
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;
}
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);
}
inline double DisPointSeg(Point C,Point A, Point B) {//点到线段的距离 计算C点到线段AB的最短距离
if (Distance(A, B) < eps) return Distance(B, C);//AB是同一点
if ((B-A)*(C-A)<-eps) return Distance(A,C);//AB*AC<0 点的投影落在线段之外且与A同侧 支点A离C最近
if ((A-B)*(C-B)<-eps) return Distance(B,C);//BA*BC<0 点的投影落在线段之外且与B同侧 支点B离C最近
Line l=Line(A,B);//点的投影落在线段内,直接计算点线距
return DisPointLine(C,l);
}
inline double DislineLine(Point A, Point B, Point C, Point D) {//求线段AB与CD之间的距离(一条线段的两端点到另外一条线段的最小距离),反过来一样,共4种情况
// cout<<"A=("<<A.x<<","<<A.y<<") B=("<<B.x<<","<<B.y<<") C=("<<C.x<<","<<C.y<<") D=("<<D.x<<","<<D.y<<")"<<endl;
// cout<<"C-l1="<<DisPointLine(C,l1)<<" D-l1="<<DisPointLine(D,l1)<<" A-l2="<<DisPointLine(A,l2)<<" B-l2="<<DisPointLine(B,l2)<<endl;
return min( min(DisPointSeg(C,A,B),DisPointSeg(D,A,B)) , min(DisPointSeg(A,C,D),DisPointSeg(B,C,D)) );
}
typedef vector<Point> Polygon;
int PointInPolygon(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;
}
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;
}
double recMeter(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)*2);
}
return res;
}
inline void anticlockwiseSort(Polygon p) {// 逆时针排序
int N=p.size();
for (int i = 0; i < N - 2; i++) {
double tmp=(p[i+1]-p[i])^(p[i+1]-p[i]);
if (tmp > eps) return;
else if (tmp < -eps) {
reverse(p.begin(), p.end());
return;
}
}
}
double minspace(Polygon P,Polygon Q) { //凸包最短间距
int yminP=0,ymaxQ=0,n=P.size(),m=Q.size();
for (int i = 0; i < n; i++) if (P[i].y < P[yminP].y) yminP = i; // P上y坐标最小的顶点
for (int i = 0; i < m; i++) if (Q[i].y > Q[ymaxQ].y) ymaxQ = i; // Q上y坐标最大的顶点
P.push_back(P[0]); // 为了方便,避免求余
Q.push_back(Q[0]);
double ans = 0x3f3f3f3f;
for (int i = 0; i < n; i++) {
while( ( (Q[ymaxQ + 1]-P[yminP + 1])^(P[yminP]-P[yminP + 1]) )-( (Q[ymaxQ]-P[yminP + 1])^(P[yminP]-P[yminP + 1]) )>eps ) ymaxQ=(ymaxQ+1)%m;
//上句代码为何不加fabs?
ans = min(ans, DislineLine(P[yminP], P[yminP + 1], Q[ymaxQ], Q[ymaxQ + 1]));
// cout<<"ymin="<< yminP<<" ymax="<<ymaxQ<<" Dis="<<DislineLine(P[yminP], P[yminP + 1], Q[ymaxQ], Q[ymaxQ + 1])<<" ans="<<ans<<endl;
yminP = (yminP + 1) % n;
}
P.pop_back(),Q.pop_back();//多加的起点退掉
return ans;
}
int main() {
int N, M;
while (~scanf("%d%d", &N, &M) && N + M) {
double x,y;Polygon ch1,ch2;Point p;
for (int i = 0; i < N; ++i) {
scanf("%lf%lf", &x, &y);
p=Point(x,y);
ch1.push_back(p);
}
for (int i = 0; i < M; ++i) {
scanf("%lf%lf", &x, &y);
p=Point(x,y);
ch2.push_back(p);
}
printf("%.5lf\n", minspace(ch1,ch2));
}
return 0;
}