#include <iostream> #include <cmath> #include <vector> #include <string.h> #include <stdlib.h> #include <stdio.h> #include <algorithm> using namespace std; #define MAX_N 110 /*------------------常量区-------------------*/ const double INF = 1e10; // 无穷大 const double EPS = 1e-10; // 计算精度 const double PI = acos(-1.0);// PI const int PINXING = 0; // 平行 const int XIANGJIAO = 1; // 相交 const int XIANGLI = 0; // 相离 const int GONGXIAN = 2; // 共线 const int CHONGDIE = -1; // 重叠 const int INSIDE = 1; // 点在图形内部 const int OUTSIDE = 0; // 点在图形外部 const int BORDER = 2; // 点在图形边界 /*-----------------类型定义区----------------*/ struct Point { // 二维点或矢量 double x, y; //double angle, dis; Point() {} Point(double x0, double y0): x(x0), y(y0) {} void read() { scanf("%lf%lf",&x,&y); } }; struct Point3D { //三维点或矢量 double x, y, z; Point3D() {} Point3D(double x0, double y0, double z0): x(x0), y(y0), z(z0) {} }; struct Line { // 二维的直线或线段 Point p1, p2; Line() {} Line(Point p10, Point p20): p1(p10), p2(p20) {} void read() { scanf("%lf%lf%lf%lf",&p1.x,&p1.y,&p2.x,&p2.y); } }; struct Line3D { // 三维的直线或线段 Point3D p1, p2; Line3D() {} Line3D(Point3D p10, Point3D p20): p1(p10), p2(p20) {} }; struct Rect { // 用长宽表示矩形的方法 w, h分别表示宽度和高度 double w, h; Rect() {} Rect(double _w,double _h) : w(_w),h(_h) {} }; struct Rect_2 { // 表示矩形,左下角坐标是(xl, yl),右上角坐标是(xh, yh) double xl, yl, xh, yh; Rect_2() {} Rect_2(double _xl,double _yl,double _xh,double _yh) : xl(_xl),yl(_yl),xh(_xh),yh(_yh) {} }; struct Circle { //圆 Point c; double r; Circle() {} Circle(Point _c,double _r) :c(_c),r(_r) {} }; typedef vector<Point> Polygon; // 二维多边形 typedef vector<Point> Points; // 二维点集 /*-------------------基本函数区---------------------*/ inline double max(double x,double y) { return x > y ? x : y; } inline double min(double x, double y) { return x > y ? y : x; } inline bool ZERO(double x) // x == 0 { return (fabs(x) < EPS); } inline bool ZERO(Point p) // p == 0 { return (ZERO(p.x) && ZERO(p.y)); } inline bool ZERO(Point3D p) // p == 0 { return (ZERO(p.x) && ZERO(p.y) && ZERO(p.z)); } inline bool EQ(double x, double y) // eqaul, x == y { return (fabs(x - y) < EPS); } inline bool NEQ(double x, double y) // not equal, x != y { return (fabs(x - y) >= EPS); } inline bool LT(double x, double y) // less than, x < y { return ( NEQ(x, y) && (x < y) ); } inline bool GT(double x, double y) // greater than, x > y { return ( NEQ(x, y) && (x > y) ); } inline bool LEQ(double x, double y) // less equal, x <= y { return ( EQ(x, y) || (x < y) ); } inline bool GEQ(double x, double y) // greater equal, x >= y { return ( EQ(x, y) || (x > y) ); } // 输出浮点数前,防止输出-0.00调用该函数进行修正! inline double FIX(double x) { return (fabs(x) < EPS) ? 0 : x; } /*------------------二维矢量运算重载区---------------------*/ bool operator==(Point p1, Point p2) { return ( EQ(p1.x, p2.x) && EQ(p1.y, p2.y) ); } bool operator!=(Point p1, Point p2) { return ( NEQ(p1.x, p2.x) || NEQ(p1.y, p2.y) ); } bool operator<(Point p1, Point p2) { if (NEQ(p1.x, p2.x)) { return (p1.x < p2.x); } else { return (p1.y < p2.y); } } Point operator+(Point p1, Point p2) { return Point(p1.x + p2.x, p1.y + p2.y); } Point operator-(Point p1, Point p2) { return Point(p1.x - p2.x, p1.y - p2.y); } double operator*(Point p1, Point p2) // 计算叉乘 p1 × p2 { return (p1.x * p2.y - p2.x * p1.y); } double operator&(Point p1, Point p2) { // 计算点积 p1·p2 return (p1.x * p2.x + p1.y * p2.y); } double Norm(Point p) // 计算矢量p的模 { return sqrt(p.x * p.x + p.y * p.y); } /*-------------------基本函数区------------------*/ //得到两点之间的距离 double Dis(Point p1,Point p2) { return sqrt( (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y) ); } //求二维平面上点到直线的距离 double Dis(Point p, Line L) { return ( fabs((p - L.p1) * (L.p2 - L.p1)) / Norm(L.p2 - L.p1) ); } //得到两点之间距离的平方,为减少误差用 double Dis2(Point p1,Point p2) { return (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y); } //返回A关于B的对称点C,即(A+C)/2=b Point SymmetryPoint(Point A,Point B) { Point C; C.x = 2*B.x-A.x; C.y = 2*B.y-A.y; return C; } // 把矢量p旋转角度angle (弧度表示) // angle > 0表示逆时针旋转 // angle < 0表示顺时针旋转 Point Rotate(Point p, double angle) { Point result; result.x = p.x * cos(angle) - p.y * sin(angle); result.y = p.x * sin(angle) + p.y * cos(angle); return result; } //得到向量p与x正半轴的夹角[0,2PI) double GetAngle(Point p) { double tmp=atan2(p.y,p.x); if(tmp<0) tmp=2*PI+tmp; return tmp; } //得到两个向量之间的夹角[0,PI] //若p1按顺时针转到p2的角小于PI(p1在p2的逆时针方向),则返回正数.否则返回负数. double GetAngle(Point p1,Point p2) { double tmp = GetAngle(p1) - GetAngle(p2); if( GT( tmp,PI) ) return -(2*PI-tmp); if( LEQ( tmp,-PI) ) return (tmp+2*PI); return tmp; } // 判断二维平面上点是否在线段上 // 输入:任意点p,和任意直线L // 输出:p在线段上返回1,否则返回0 bool OnSeg(Point p, Line L) { return ( ZERO( (L.p1 - p) * (L.p2 - p) ) && LEQ((p.x - L.p1.x)*(p.x - L.p2.x), 0) && LEQ((p.y - L.p1.y)*(p.y - L.p2.y), 0) ); } // 判断二维平面上点p是否在直线L上,在线段上返回1,否则返回0 bool OnLine(Point p, Line L) { return ZERO( (p - L.p1) * (L.p2 - L.p1) ); } bool OnCir(Point p,Circle cir) { return EQ( (p.x-cir.c.x)*(p.x-cir.c.x)+(p.y-cir.c.y)*(p.y-cir.c.y),cir.r*cir.r ); } //得到点p到直线L的距离,并返回p到直直线L的最近点rep double PointToLine(Point p,Line L,Point& rep) { if(L.p1==L.p2) { rep=L.p1; return Dis(p,L.p1); } Point a,b; a = L.p2-L.p1; b = p-L.p1; double dis12 = Dis(L.p1,L.p2); double dis = ( fabs(a*b) )/dis12; double k = (a&b)/(Norm(a)*dis12) ; rep.x = L.p1.x + k*(L.p2.x-L.p1.x); rep.y = L.p1.y + k*(L.p2.y-L.p1.y); return dis; } //得到点P到线段L的距离,并放回p到线段L的最近点rep double PointToSeg(Point P, Line L,Point& rep) { if(L.p1 == L.p2) { rep = L.p1; return Dis(rep,P);//如果线段是一个点,返回这个点。 } Point result; double a, b, t; a = L.p2.x - L.p1.x; b = L.p2.y - L.p1.y; t = ( (P.x - L.p1.x) * a + (P.y - L.p1.y) * b ) / (a * a + b * b);//线段上的投影 if ( GEQ(t, 0) && LEQ(t, 1) ) { result.x = L.p1.x + a * t;//值得学习的由比例求坐标的方法。 result.y = L.p1.y + b * t; } else { if ( Norm(P - L.p1) < Norm(P - L.p2) ) { result = L.p1; } else { result = L.p2; } } return Dis(result, P); } //返回点A关于直线L的对称点 Point SymmetryPonitToLine(Point A,Line L) { Point B; PointToLine(A, L, B);//A在L上的投影 return SymmetryPoint(A, B); } /*-------------------几何题面积计算(注意正负!)----------------------*/ // 根据三个顶点坐标计算三角形面积 // 面积的正负按照右手旋规则确定,向量AB->向量AC double Area(Point A, Point B, Point C) { return ((B-A)*(C-A) / 2.0); } // 根据三条边长计算三角形面积 double Area(double a, double b, double c) { double s = (a + b + c) / 2.0; return sqrt(s * (s - a) * (s - b) * (s - c)); } //求圆的面积 double Area(Circle C) { return PI * C.r * C.r; } // 计算多边形面积,复杂度:O(顶点数) // 面积的正负按照右手旋规则确定,顺时针为负 double Area(Polygon _poly) { int nsize=_poly.size(); double area=0; for(int i=0;i<nsize;i++) { area += _poly[i]*_poly[(i+1)%nsize]; } return area/2.0; } //求两条直线之间的关系(二维) //输入:两条不为点的直线 //输出:相交返回XIANGJIAO和交点p,平行返回PINGXING,共线返回GONGXIAN int LineAndLine(Line L1,Line L2,Point &p) { Point px,py; px = L1.p1 - L1.p2; py = L2.p1 - L2.p2; if( EQ(px*py,0) )//平行或者共线 { if( ZERO( (L2.p1-L1.p1)*py ) ) //共线 { return GONGXIAN; } return PINXING; } double xa,xb,xc,ya,yb,yc; xa=(L1.p2.y-L1.p1.y); xb=(L1.p1.x-L1.p2.x); xc=(L1.p1.y*L1.p2.x-L1.p1.x*L1.p2.y); ya=(L2.p2.y-L2.p1.y); yb=(L2.p1.x-L2.p2.x); yc=(L2.p1.y*L2.p2.x-L2.p1.x*L2.p2.y); p.y = (xa*yc-xc*ya)/(xb*ya-xa*yb); p.x = (xb*yc-xc*yb)/(xa*yb-xb*ya); return XIANGJIAO; } //判断两条线段是否相交,相交返回1 bool SegAndSeg(Line L1,Line L2) { return ( GEQ( max(L1.p1.x, L1.p2.x), min(L2.p1.x, L2.p2.x) ) && GEQ( max(L2.p1.x, L2.p2.x), min(L1.p1.x, L1.p2.x) ) && GEQ( max(L1.p1.y, L1.p2.y), min(L2.p1.y, L2.p2.y) ) && GEQ( max(L2.p1.y, L2.p2.y), min(L1.p1.y, L1.p2.y) ) && LEQ( ((L2.p1 - L1.p1) * (L1.p2 - L1.p1)) * ((L2.p2 - L1.p1) * (L1.p2 - L1.p1)), 0 ) && LEQ( ((L1.p1 - L2.p1) * (L2.p2 - L2.p1)) * ((L1.p2 - L2.p1) * (L2.p2 - L2.p1)), 0 ) ); } //求两条线段交点(二维) //输入:两条不为点的直线 //输出:相交返回XIANGJIAO和交点p,相离返回XIANGLI,重叠返回CHONGDIE int SegAndSeg(Line L1,Line L2,Point &p) { double signx,signy; //跨立实验 if( LEQ(signx=( ((L1.p2-L1.p1)*(L1.p1-L2.p1))*((L1.p2-L1.p1)*(L1.p1-L2.p2)) ),0) && LEQ(signy=( ((L2.p2-L2.p1)*(L2.p1-L1.p1))*((L2.p2-L2.p1)*(L2.p1-L1.p2)) ),0) ) { if( ZERO(signx) && ZERO(signy) ) { //线段共线 signx = min( max(L1.p1.x,L1.p2.x),max(L2.p1.x,L2.p2.x) )- max( min(L1.p1.x,L1.p2.x),min(L2.p1.x,L2.p2.x) ); signy = min( max(L1.p1.y,L1.p2.y),max(L2.p1.y,L2.p2.y) )- max( min(L1.p1.y,L1.p2.y),min(L2.p1.y,L2.p2.y) ); if( ZERO(signx) && ZERO(signy) ) //说明共线,且相交一点 { if(L1.p1==L2.p1||L1.p1==L2.p2) p=L1.p1; if(L1.p2==L2.p1||L1.p2==L2.p2) p=L1.p2; return XIANGJIAO; } else if( GEQ(signx, 0) && GEQ(signy, 0) ) { return CHONGDIE;//重叠 } else { return XIANGLI;//相离 } } return LineAndLine(L1, L2, p);//转化为直线相交 } return XIANGLI;//相离 } // 判断点p是否在简单多边形poly内, 多边形可以是凸的或凹的 // poly的顶点数目要大于等于3 // 返回值为: // INSIDE -- 点在poly内 // BORDER -- 点在poly边界上 // OUTSIDE -- 点在poly外 int InPolygon(const Polygon poly, Point p) { int i, n, count; Line ray, side; n = poly.size(); count = 0; ray.p1 = p; ray.p2.y = p.y; ray.p2.x = - INF;// 设定一个极大值 for (i = 0; i < n; i++) { side.p1 = poly[i]; side.p2 = poly[(i+1)%n]; if( OnSeg(p, side) ) { return BORDER; } // 如果side平行x轴则不作考虑 if ( EQ(side.p1.y, side.p2.y) ) { continue; } if (OnSeg(side.p1, ray)) { if ( GT(side.p1.y, side.p2.y) ) count++; } else if (OnSeg(side.p2, ray)) { if ( GT(side.p2.y, side.p1.y) ) count++; } else if ( SegAndSeg(ray, side) ) { count++; } } return ((count % 2 == 1) ? INSIDE : OUTSIDE); } //得到直线与圆的交点 int LineToCir(Line L,Circle R,Point p[2]) { if(L.p1 == L.p2)//当直线为1个点时 { if( EQ( Dis(L.p1, R.c),R.r ) ) { p[0]=L.p1; return 1; } else return 0;//相离 } Point tp;//表示圆心在直线L上的投影。 double dis=PointToLine(R.c, L,tp ); if( LT(R.r, dis) )//相离 { return 0; } if( EQ(dis,R.r) )//相切 { p[0]=tp; return 1; } double len=sqrt(R.r*R.r-dis*dis); Point onep=L.p2-L.p1; double _t = len/Norm(onep); p[0].x =tp.x + onep.x*_t; p[0].y =tp.y + onep.y*_t; onep=L.p1-L.p2; p[1].x =tp.x + onep.x*_t; p[1].y =tp.y + onep.y*_t; return 2; } //得到三角形外接圆 //注意:A,B,C三点不能共线 Circle OutCircle(Point A,Point B,Point C) { Circle tmp; double a, b, c, c1, c2; double xA, yA, xB, yB, xC, yC; a = Dis(A, B); b = Dis(B, C); c = Dis(C, A); //根据 S = a * b * c / R / 4;求半径 R tmp.r = (a*b*c)/( fabs(Area(A,B,C)) *4.0); xA = A.x; yA = A.y; xB = B.x; yB = B.y; xC = C.x; yC = C.y; c1 = (xA*xA+yA*yA - xB*xB-yB*yB) / 2; c2 = (xA*xA+yA*yA - xC*xC-yC*yC) / 2; tmp.c.x = (c1*(yA - yC)-c2*(yA - yB)) / ((xA - xB)*(yA - yC)-(xA - xC)*(yA - yB)); tmp.c.y = (c1*(xA - xC)-c2*(xA - xB)) / ((yA - yB)*(xA - xC)-(yA - yC)*(xA - xB)); return tmp; } //得到三角形内切圆 //注意:A,B,C三点不能共线 Circle InCircle(Point A,Point B,Point C) { Circle rec; double a=Dis(B,C); double b=Dis(A,C); double c=Dis(A,B); rec.c.x = (a*A.x+b*B.x+c*C.x)/(a+b+c); rec.c.y = (a*A.y+b*B.y+c*C.y)/(a+b+c); rec.r = 2*fabs( Area(A,B,C) )/(a+b+c); return rec; } //得到两圆的面积并 double CirArea(Circle _c1,Circle _c2) { if(_c2.r<_c1.r) swap(_c1,_c2); //保证_c2的半径大 double d12 = Dis(_c1.c,_c2.c); if( LEQ(_c1.r+_c2.r,d12) )//相离 { return 0; } if( LEQ(d12+_c1.r, _c2.r) )//包含 { return Area(_c1); } //相交 double _area=0; _area -= 2.0*Area(_c1.r,_c2.r,d12); double ang1 = acos( (d12*d12+_c1.r*_c1.r-_c2.r*_c2.r) / (2*d12*_c1.r) ); double ang2 = acos( (d12*d12+_c2.r*_c2.r-_c1.r*_c1.r) / (2*d12*_c2.r) ); _area += ang1*_c1.r*_c1.r+ang2*_c2.r*_c2.r; return _area; } //得到两个圆的交点p[2] //返回值为交点数,-1为两圆重叠。 int CirAndCir(Circle _c1,Circle _c2,Point p[2]) { if(_c2.r < _c1.r) swap(_c1,_c2); //保证_c2的半径大 double d12 = Dis(_c1.c,_c2.c); if( LT(_c1.r+_c2.r,d12) )//相离 { return 0; } if( LT(d12+_c1.r, _c2.r) )//包含 { return 0; } if(_c1.c == _c2.c)//两个圆重叠 { return -1; } Point u,v; double t; double r1=_c1.r,r2=_c2.r; t=( 1+(r1*r1-r2*r2)/(Dis(_c1.c,_c2.c)*Dis(_c1.c,_c2.c)) ) /2; u.x = _c1.c.x + (_c2.c.x-_c1.c.x)*t; u.y = _c1.c.y + (_c2.c.y-_c1.c.y)*t; v.x = u.x + _c1.c.y - _c2.c.y; v.y = u.y - _c1.c.x + _c2.c.x; Line _l(u,v); return LineToCir(_l,_c1,p); } //求凸包Graham-Scan(GS)算法,复杂度nlog(n),内附两种模式,最小点集凸包,与最大点集凸包。 //调用GetConvex_GS(Point ps[],int pn,Point cx[],int &cxn) //其中ps是输入的点集,pn为ps的大小,cx为返回的凸包集,cxn表示凸包的大小。 //注意:pn必须>=3,下标都是从0开始,返回的凸包集为逆时针。且如果点带标号,一样处理。 int GScmp(Point a,Point b) { double _tmp=a*b; Point zero; zero.x=0; zero.y=0; if( ZERO(_tmp) ) { return Dis2(a,zero)>Dis2(b,zero); } return _tmp > 0; } void GetConvex_GS(Point ps[],int pn,Point cx[],int &cxn) { Polygon pg; pg.clear(); //先找出最下面,如果有相同的找最靠左,也就找y轴最小的,然后y相同时选x最小。 Point minp=ps[0]; for(int i=1;i<pn;i++) { if( ps[i].y < minp.y ) { minp = ps[i]; } else if(EQ(ps[i].y, minp.y) && ps[i].x < minp.x) { minp = ps[i]; } } for(int i=0;i<pn;i++) { ps[i].x -= minp.x; ps[i].y -= minp.y; pg.push_back( ps[i] ); } //排序 sort(pg.begin(),pg.end(),GScmp); //在这一步,除去与minp共线的点。 long int pgsize=pg.size(); int pgcnt=1; for(int i=1;i<pgsize;i++) { if( !ZERO(pg[i]*pg[pgcnt-1]) ) { pg[ pgcnt++ ] = pg[i]; } } cxn = 0; cx[ cxn++ ] = minp; if( !(ZERO(pg[0].x) && ZERO(pg[0].y)) )//不为一点时 { pg[0].x += minp.x; pg[0].y += minp.y; cx[ cxn++ ] = pg[0];//必须保存id for(int i=1;i < pgcnt;i++) { pg[i].x += minp.x; pg[i].y += minp.y; double _tmp = (cx[cxn-1]-cx[cxn-2])*(pg[i]-cx[cxn-1]); // 无法处理重复点! // nice ! while( LEQ(_tmp, 0) ) { cxn--; //if(cxn==1) break;//只剩下一个的时候,退出 _tmp = (cx[cxn-1]-cx[cxn-2])*(pg[i]-cx[cxn-1]); } cx[ cxn++ ] = pg[i]; } } //已经找到了,最小凸包集,且为逆时针排序。 /* //接下来步骤为找出所有在凸包边界上的点,并按逆时针排序。 sort(ps,ps+pn,GScmp); int cxn1=0; int tj=0; for(int i=0;i<pn;i++) { ps[i].x += minp.x; ps[i].y += minp.y; Line l(cx[tj],cx[(tj+1)%cxn]); if( OnSeg(ps[i], l) ) { ps[ cxn1++ ] = ps[i]; continue; } if( tj+1<cxn && GEQ( (cx[tj+1]-minp)*(ps[i]-minp),0 ) ) { tj++; } l.p1 = cx[ tj ]; l.p2 = cx[ (tj+1)%cxn ]; if(OnSeg(ps[i], l)) ps[cxn1++]=ps[i]; } cxn=cxn1; for(int i=0;i<cxn1;i++) cx[i]=ps[i]; //接下来的步骤是,将凸包点集调整为逆时针,其实只需要调整斜率最大的一条边。 tj=1; Line l(minp,cx[0]); while(tj<cxn && OnSeg(cx[tj], l)) tj++; tj--; for(int i=0;i<=tj;i++) cx[i] = ps[tj-i]; //////////////////////////////////// */ } /*---------------------代码区---------------------------*/ //what the fuck double ans; //返回两条线段的最短距离 double Dis(Line l1,Line l2) { Point p; return min( min(PointToSeg(l1.p1, l2,p),PointToSeg(l1.p2, l2, p)) ,min(PointToSeg(l2.p1, l1,p),PointToSeg(l2.p2, l1, p)) ); } void CirAndCut(Point psn[],int n,Point psm[],int m) { int nid=0,mid=0; for(int i=1;i<n;i++) if(psn[i].y>psn[nid].y) { nid=i; } for(int i=1;i<m;i++) if(psm[i].y<psm[mid].y) { mid=i; } //找到点集n中最上点,m中的最下点。 //l 为水平向右的向量 for(int ii=0;ii<n+m;ii++) { //第一步判断谁先 滚起来 Point nextn,nextm; nextn = psn[(nid+1)%n]; nextm = psm[(mid+1)%m]; if( (nextn-psn[nid])*(psm[mid]-nextm)>0 ) { //n先滚 nid = (nid+1)%n; } else { mid= (mid+1)%m; } //这里就可以得到对踵点 Line l1,l2; l1.p1 = psn[nid]; l1.p2 = psn[ (nid-1+n)%n ]; l2.p1 = psm[mid]; l2.p2 = psm[ (mid-1+m)%m ]; ans = min(ans,Dis(l1, l2)); } } Point psn[10010],psm[10010]; int main(int argc, const char * argv[]) { int n,m; while( scanf("%d%d",&n,&m) && (n+m) ) { for(int i=0;i<n;i++) psn[i].read(); for(int i=0;i<m;i++) psm[i].read(); Point cn[10010],cm[10010]; int cntn,cntm; GetConvex_GS(psn, n, cn, cntn); GetConvex_GS(psm, m, cm, cntm); ans = INF; /* if(cntn<=3||cntm<=3) { for(int i=0;i<cntn;i++) { for(int j=0;j<cntm;j++) { Line l(cm[j],cm[(j+1)%cntm]); Point p; ans = min(ans,PointToSeg(cn[i], l, p)); } } for(int i=0;i<cntm;i++) { for(int j=0;j<cntn;j++) { Line l(cn[j],cn[(j+1)%cntn]); Point p; ans = min(ans,PointToSeg(cm[i], l, p)); } } } else */ CirAndCut(cn,cntn,cm,cntm); printf("%.6lf ",ans); } return 0; } /* 5 5 1 -1 2 -1 3 -1 2 -2 3 -2 -1 1 -2 1 -3 1 -2 2 -3 2 5 5 -1 1 -2 1 -3 1 -2 2 -3 2 1 -1 2 -1 3 -1 2 -2 3 -2 */