二维几何基础
#include
using namespace std;
const int MAXN = 1e5+5;
const int INF = 0x3f3f3f3f;
const double eps = 1e-8;
const double PI = acos(-1.0);
/**
atan2(y,x)返回反正切值,[-PI,PI]弧度,求极角
atan(x)返回反正切值,[-PI/2,PI/2]弧度
**/
struct Point{
double x,y;
Point(double _x=0,double _y=0):x(_x),y(_y){}
};
typedef Point Vector;
//向量+向量=向量,点+向量=点
Vector operator + (Vector A,Vector B){return Vector(A.x+B.x,A.y+B.y);}
//点-点=向量
Vector operator - (Point A,Point B){return Vector(A.x-B.x,A.y-B.y);}
//向量*数=向量
Vector operator * (Vector A,double p) {return Vector(A.x*p,A.y*p);}
//向量/数=向量
Vector operator / (Vector A,double p) {return Vector(A.x/p,A.y/p);}
bool operator < (const Point& a,const Point& b){
return a.xint dcmp(double x){
if(fabs(x)return 0;
else return x<0?-1:1;
}
bool operator == (const Point& a,const Point& b){
return dcmp(a.x-b.x)==0 && dcmp(a.y-b.y)==0;
}
int main(){
return 0;
}
基本运算
//点积,v·w=|v||w|cos(sita),满足交换律
double Dot(Vector A,Vector B) { return A.x*B.x+A.y*B.y; }
double Length(Vector A) { return sqrt(Dot(A,A)); }
double Angle(Vector A,Vector B) { return acos(Dot(A,B)/Length(A)/Length(B)); }
//叉积,组成三角形的有向面积的两倍,不满足交换律,cross(v,w)=-cross(w,v)
double Cross(Vector A,Vector B) { return A.x*B.y-A.y*B.x; }
double Area2(Point A,Point B,Point C) { return Cross(B-A,C-A); }
//两向量的位置关系
//假设第一个向量水平向右
//则第二个向量的情况为:(点积符号,叉积符号)
//x轴水平向右:(+,0)
//第一象限:(+,+)
//y轴竖直向上:(0,+)
//第二象限:(-,+)
//x轴水平向左:(-,0)
//第三象限:(-,-)
//y轴竖直向下:(0,-)
//第四象限:(+,-)
//向量旋转,绕起点
//x'=xcosa-ysina,
//y'=xsina+ycosa
//a为逆时针旋转的角
Vector Rotate(Vector A,double rad){
return Vector(A.x*cos(rad)-A.y*sin(rad),A.x*sin(rad)+A.y*cos(rad));
}
//计算向量的单位法线,即左转90°,之后把长度归一化
//调用前确保A不是零向量
Vector Normal(Vector A){
double L = Length(A);
return Vector(-A.y/L,A.x/L);
}
点和直线
//有向直线。它的左边就是对应的平面
struct Line{
Point p;
Vector v;
double ang;
Line(){}
Line(Point p,Vector v):p(p),v(v){ang=atan2(v.y,v.x);}
Point point(double t) {return p+v*t;}
bool operator < (const Line& L) const{//排序用的比较运算符
return ang < L.ang;
}
};
//直线交点
//使用前确保两条直线P+tv和Q+tw有唯一交点
//当且仅当Cross(v,w)非0
Point GetLineIntersection(Point P,Vector v,Point Q,Vector w){
Vector u = P-Q;
double t = Cross(w,u)/Cross(v,w);
return P+v*t;
}
//点到线段的距离,判断投影点是否在线段上
double DistanceToSegment(Point P,Point A,Point B){
if(A==B) return Length(P-A);
Vector v1=B-A,v2=P-A,v3=P-B;
if(dcmp(Dot(v1,v2))<0) return Length(v2);
else if(dcmp(Dot(v1,v3))>0) return Length(v3);
else return fabs(Cross(v1,v2))/Length(v1);
}
//点在直线上的投影
Point GetLineProjection(Point P,Point A,Point B){
Vector v=B-A;
return A+v*(Dot(v,P-A)/Dot(v,v));
}
// 线段相交判定
// 规范相交:两线段恰好有一个公共点,且不在任何一条线段的端点
bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2){
double c1 = Cross(a2-a1,b1-a1),c2 = Cross(a2-a1,b2-a1),
c3 = Cross(b2-b1,a1-b1),c4 = Cross(b2-b1,a2-b1);
return dcmp(c1)*dcmp(c2)<0 && dcmp(c3)*dcmp(c4)<0;
}
//判断一个点是否在一条线段上(不包含端点)
bool OnSegment(Point p,Point a1,Point a2){
return dcmp(Cross(a1-p,a2-p))==0 && dcmp(Dot(a1-p,a2-p))<0 ;
}
多边形
//计算多边形的有向面积
double ConvexPolygonArea(Point *p,int n){
double area = 0;
for(int i=1;i1;++i){
area += Cross(p[i]-p[0],p[i+1]-p[0]);
}
return area/2;
}
//多边形的有向面积
double PolygonArea(Point *p,int n){
double area = 0;
for(int i=1;i1;++i){
area+=Cross(p[i]-p[0],p[i+1]-p[0]);
}
return area/2;
}
与圆和球有关的计算问题
struct Circle{
Point c;
double r;
Circle(Point c,double r):c(c),r(r){}
Point point(double a){//通过圆心角求坐标
return Point(c.x+cos(a)*r,c.y+sin(a)*r);
}
};
//直线与圆的交点
//设交点P=A+t(B-A),代入圆方程整理的(at+b)^2+(ct+d)^2=r^2
//进一步整理得 et^2+ft+g=0,根据判别式的值判断
//函数返回的是交点的个数,参数sol存放的是交点本身。
//上述代码并没有清空sol,可以反复调用这个函数,把所有交点放在一个sol里
int getLineCircleIntersection(Line L,Circle C,double& t1,double& t2,vector & sol){
double a = L.v.x, b = L.p.x - C.c.x, c = L.v.y, d = L.p.y - C.c.y;
double e = a*a + c*c, f = 2*(a*b + c*d), g = b*b + d*d -C.r*C.r;
double delta = f*f - 4*e*g; //判别式
if(dcmp(delta) < 0) return 0; //相离
if(dcmp(delta) == 0){ //相切
t1 = t2 = -f /(2*e);
sol.push_back(L.point(t1));
return 1;
}
//相交
t1 = (-f - sqrt(delta)) / (2*e);
sol.push_back(L.point(t1));
t2 = (-f + sqrt(delta)) / (2*e);
sol.push_back(L.point(t2));
return 2;
}
//计算向量极角
double angle(Vector v) {return atan2(v.y,v.x);}
//两圆相交
double getCircleCircleIntersection(Circle C1,Circle C2,vector & sol){
double d = Length(C1.c - C2.c);
if(dcmp(d) == 0){
if(dcmp(C1.r-C2.r)==0) return -1;//两圆重合
return 0;
}
if(dcmp(C1.r+C2.r-d)<0) return 0;
if(dcmp(fabs(C1.r-C2.r)-d)>0) return 0;
double a = angle(C2.c - C1.c); //向量C1C2的极角
double da = acos((C1.r*C1.r+d*d-C2.r*C2.r)/(2*C1.r*d));
//C1C2到C2P1的角
Point p1 = C1.point(a-da), p2 = C1.point(a+da);
sol.push_back(p1);
if(p1 == p2) return 1;
sol.push_back(p2);
return 2;
}
//过定点作圆的切线
//过点p到圆C的切线,v[i]是第i条切线的向量,返回切线条数
int getTangents(Point p,Circle C,Vector* v){
Vector u = C.c - p;
double dist = Length(u);
if(dist < C.r) return 0;
else if(dcmp(dist - C.r) == 0){//p在圆上,只有一条切线
v[0]=Rotate(u,PI/2);
return 1;
}else{
double ang = asin(C.r / dist);
v[0] = Rotate(u,-ang);
v[1] = Rotate(u,+ang);
return 2;
}
}
/**
两圆的公切线。根据两圆的圆心距从小到大排列,
一共有6种情况:
情况一:两圆完全重合,有无数条公切线
情况二:两圆内含,没有公共点,没有公切线
情况三:两圆内切,有1条外公切线
情况四:两圆相交,有2条外公切线
情况五:两圆外切,有3条公切线,1条内公切线,2条外公切线
情况六:两圆相离,有4条公切线,其中内公切线2条,外公切线2条
情况三和五:连接圆心和切点,旋转90度
**/
//返回切线的条数。-1表示无穷条切线
//a[i]和b[i]分别是第i条切线在圆A和圆B上的切点
int getTangents(Circle A,Circle B,Point *a,Point *b){
int cnt = 0;
if(A.r < B.r) {swap(A,B); swap(a,b); }
double d2 = (A.c.x-B.c.x)*(A.c.x-B.c.x) + (A.c.y-B.c.y)*(A.c.y-B.c.y);
int rdiff = A.r-B.r;
int rsum = A.r+B.r;
if(d2 < rdiff*rdiff) return 0; //内含
double base = atan2(B.c.y-A.c.y,B.c.x-A.c.x);
if(d2 == 0 && A.r == B.r) return -1; //无限多条切线
if(d2 == rdiff*rdiff){ //内切,1条切线
a[cnt] = A.point(base);
b[cnt] = B.point(base);
cnt++;
return 1;
}
//有外公切线
double ang = acos((A.r-B.r) / sqrt(d2));
a[cnt] = A.point(base+ang);
b[cnt] = B.point(base+ang);
cnt++;
a[cnt] = A.point(base-ang);
b[cnt] = B.point(base-ang);
cnt++;
if(d2 == rsum*rsum){ //一条公切线
a[cnt] = A.point(base);
b[cnt] = B.point(PI+base);
cnt++;
}else if(d2 > rsum*rsum){ //两条公切线
double ang = acos((A.r+B.r) / sqrt(2));
a[cnt] = A.point(base+ang);
b[cnt] = B.point(PI+base+ang);
cnt++;
a[cnt] = A.point(base-ang);
b[cnt] = B.point(PI+base-ang);
cnt++;
}
return cnt;
}
//球面相关问题
//角度转换成弧度
double torad(double deg){
return deg/180 * acos(-1);
}
//经纬度(角度)转化为空间坐标
//经度 longitude 纬度 latitude
void get_coord(double R,double lat,double lng,double& x,double& y,double& z){
lat = torad(lat);
lng = torad(lng);
x = R*cos(lat)*cos(lng);
y = R*cos(lat)*sin(lng);
z = R*sin(lat);
}
二维几何常用算法
typedef vector Polygon;
int isPointInPolygon(Point p,Polygon poly){
int wn = 0;
int n = poly.size();
for(int i=0;iif(OnSegment(p,poly[i],poly[(i+1)%n])) return -1; //在边界上
int k = dcmp(Cross(poly[(i+1)%n]-poly[i],p-poly[i]));
int d1 = dcmp(poly[i].y - p.y);
int d2 = dcmp(poly[(i+1)%n].y-p.y);
if(k>0 && d1<=0 && d2>0) wn++; //绕数 winding number
if(k<0 && d2<=0 && d1>0) wn--;
}
if(wn!=0) return 1; //内部
return 0; //外部
}
//点在凸多边形内的判定更简单,
//只需判断是否在所有边的左边
//假设各点按照逆时针顺序排列
//凸包
//基于水平序的Andrew算法
//首先把所有点按照x从小到大排序
//(如果x相同,按照y从小到大排序)
//删除重复点后得到序列p1,p2,...
//时间复杂度O(nlogn)
//计算凸包,输入点数组p,个数为n,输出点数组ch.
//函数返回凸包顶点数
//输入不能有重复点。函数执行完之后输入点的顺序被破坏
//如果不希望在凸包的边上有输入点,把两个<=改成<
//在精度要求高时建议用dcmp比较
int ConvexHull(Point* p,int n,Point* ch){
sort(p,p+n); //先比较x坐标,再比较y坐标
int m = 0;
for(int i=0;iwhile(m>i&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--;
ch[m++] = p[i];
}
int k=m;
for(int i=n-2;i>=0;i--){
while(m>k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--;
ch[m++]=p[i];
}
if(n>1) m--;
return m;
}
//半平面交
//点p在有向直线L的左边(线上不算)
bool OnLeft(Line L,Point p){
return Cross(L.v,p-L.p) > 0;
}
//两直线交点。假定交点唯一存在
Point GetIntersection(Line a,Line b){
Vector u = a.p-b.p;
double t = Cross(b.v,u) / Cross(a.v,b.v);
return a.p+a.v*t;
}
//半平面交的主过程
int HalfplaneIntersection(Line* L,int n,Point* poly){
sort(L,L+n); //按极角排序
int first,last; //双端队列的第一个元素和最后一个元素的下标
Point *p = new Point[n]; //p[i]为q[i]和q[i+1]的交点
Line *q = new Line[n]; //双端队列
q[first=last=0]=L[0]; //双端队列初始化为只有一个半平面L[0]
for(int i=1;iwhile(first<last&&!OnLeft(L[i],p[last-1])) last--;
while(first<last&&!OnLeft(L[i],p[first])) first++;
q[++last] = L[i];
if(fabs(Cross(q[last].v,q[last-1].v))//两向量平行且同向,取内侧的一个
last--;
if(OnLeft(q[last],L[i].p)) q[last]=L[i];
}
if(first<last) p[last-1] = GetIntersection(q[last-1],q[last]);
}
while(first<last&&!OnLeft(q[first],p[last-1])) last--;
//删除无用平面(*)
if(last-first<=1) return 0; //空集(**)
p[last] = GetIntersection(q[last],q[first]); //计算首尾两个半平面的交点
//从deque复制到输出中
int m = 0;
for(int i=first;i<=last;i++) poly[m++] = p[i];
return m;
}