计算几何板子

  1. 几何
    1. 误差消除
    2. 直线
    3. 点到直线的距离
    4. 点在直线上的投影点
    5. 求两直线的交点
    6. 求三角形面积
    7. 求多边形的面积
    8. 凸包

计算几何模板

几何

struct Point {
    double x, y;
    Point() : x(0), y(0) {}
    Point(double _x, double _y) : x(_x), y(_y) {}
    Point operator+(Point& p) { return { x + p.x, y + p.y }; }
    Point operator-(Point& p) { return { x - p.x, y - p.y }; }
    double operator*(Point& p) { return x * p.x + y * p.y; } //点积
    double operator^(Point& p) { return x * p.y - y * p.x; } //叉积
};

误差消除

const double eps=1e-9;
int dcmp(double x){
    return (fabs(x)<=eps)?0:(x<0?-1:1);
}

直线

struct Line{
	//ax+by+c=0
	double a,b,c;
	//u为直线上一点,v为方向向量
	Point u,v;
	Line(){}
	//两点确定的直线方程
	Line(Point p,Point q){
		a=p.y-q.y;b=q.x-p.x;c=p.x*q.y-q.x*p.y;
		//保证u、v两点逆时针排列
		if((p^q)<0) swap(p,q);
		u=p;v=q-p;
	}
};

点到直线的距离

double disPL(Point u,Line l){
	double length;
	length = abs(l.a*u.x+l.b*u.y+l.c)/(sqrt(l.a*l.a+l.b*l.b));
	return length;
}

点在直线上的投影点

point proPointLine(Point u,Line l){
	point v;
	double t=(-u.x*l.a-u.y*l.b-l.c)/(l.a*l.a+l.b*l.b);
	v.x=u.x+l.a*t;
	v.y=u.y+l.b*t;
	return v;
}

求两直线的交点

Point itsLineLine(Line l1,Line l2){
	Point p;
	double k = l1.a*l2.b-l1.b*l2.a;
	p.x = -(l1.c*l2.b-l1.b*l2.c)/k;
	p.y = -(l1.a*l2.c-l1.c*l2.a)/k;
	return p;
}

求三角形面积

//求三角形面积
double triarea(Point u,Point v,Point w){
	//叉积方法
	return abs((v-u)^(w-u))/2.0;
}
double triarea(double a,double b,double c){
	//海伦公式
	double p=(a+b+c)/2;
	return sqrt(p*(p-a)*(p-b)*(p-c));
}

求多边形的面积

//求多边形的面积
double polygonArea(point *u,int size){
	double area=0;
	point begin=u[1];
	/*
	  由第一个点起始的顺序叉积,其中,点可以无序,
	  面积值为边之间连线的封闭部分,叉积能够计算容斥部分
	 */
	for(int i=3;i<=size;i++) area+=((u[i-1]-begin)^(u[i]-begin))/2;
	return area;
}

//圆结构
struct circle{
	//圆心
	point cc;
	//半径
	double radius;
};
//求三点所确定的圆
circle concyclic(point u,point v,point w){
	circle c;
	point o;
	double k=2*(v.x-u.x)*(w.y-v.y)-2*(v.y-u.y)*(w.x-v.x);
	o.x=(w.y-v.y)*(v.x*v.x+v.y*v.y-u.x*u.x-u.y*u.y)-(v.y-u.y)*(w.x*w.x+w.y*w.y-v.x*v.x-v.y*v.y);
	o.y=(v.x-u.x)*(w.x*w.x+w.y*w.y-v.x*v.x-v.y*v.y)-(w.x-v.x)*(v.x*v.x+v.y*v.y-u.x*u.x-u.y*u.y);
	o.x/=k;o.y/=k;
	c.cc=o;c.radius=disPointPoint(o,u);
	return c;
}
//求圆与直线的交点
vector<point> itsStrCir(line l,circle c){
	double k=l.u*l.v,a=norm(l.u),b=norm(l.v),r=c.radius;
	double d=k*k-b*b*(a*a-r*r);
	vector<point>ans;
	if(d==0) ans.push_back(l.u+l.v*(-k/(b*b)));
	else {
		ans.push_back(l.u+l.v*((-k+d)/(b*b)));
		ans.push_back(l.u+l.v*((-k-d)/(b*b)));
	}
	return ans;
}
//求两圆的交点
vector<point> itsCirCir(circle c1,circle c2){
	vector<point> ans;
	point o1=c1.cc,o2=c2.cc,a=o2-o1,b;
	b.x=a.y;b.y=-a.x;
	double r1=c1.radius,r2=c2.radius;
	double d=disPointPoint(o1,o2);
	double S=triarea(r1,r2,d);
	double h=2*S/d;
	double t=sqrt(r1*r1-h*h);
	//目标两点的中垂线与O1O2反向
	if(r1*r1+d*d<r2*r2) t=-t;
	//不能用半径和来判断,否则会漏掉内切的情况
	if(h==0) ans.push_back(o1+a*t/norm(a));
	else {
		ans.push_back(o1+a*t/norm(a)+b*h/norm(b));
		ans.push_back(o1+a*t/norm(a)-b*h/norm(b));
	}
	return ans;
}
//求一点与圆的切线
vector<line> tlPointCircle(point u,circle c){
	vector<line> ans;
	circle o;
	o.cc=(c.cc+u)/2;
	o.radius=disPointPoint(c.cc,u)/2;
	vector<point> p=itsCirCir(o,c);
	if(p.size()==1) {
		point v;
		v.x=(u-c.cc).y;v.y=-(u-c.cc).x;
		ans.push_back(line(u,u+v));
	}
	if(p.size()==2){
		ans.push_back(line(p[0],u));
		ans.push_back(line(p[1],u));
	}
	return ans;
}
//求两圆的公切线
vector<line> comTangent(circle c1,circle c2){
	vector<line> ans,q;
	int r1=c1.radius,r2=c2.radius;
	int d=disPointPoint(c1.cc,c2.cc);
	point u,v,a=c2.cc-c1.cc,t;
	if(r1==r2){
		u=c1.cc-c2.cc;
		v.x=u.y;v.y=-u.x;
		ans.push_back(line(c1.cc+v*r1/norm(v),c1.cc+v*r1/norm(v)+u));
		ans.push_back(line(c1.cc-v*r1/norm(v),c1.cc-v*r1/norm(v)+u));
	}
	else {
		//内侧切线
		if(triarea(r1,r2,d)==0){
			t=c1.cc+a*r1/r2;
			q=tlPointCircle(t,c1);
			while(q.size()) ans.push_back(q.back()),q.pop_back();
		}
		//外侧切线
		t=c1.cc+a*r1/(r1-r2);
		q=tlPointCircle(t,c1);
		while(q.size()) ans.push_back(q.back()),q.pop_back();
	}
	return ans;
}
//最小圆覆盖
circle Smallestcir(point *u,int size){
	random_shuffle(u+1,u+1+size);
	point o=u[1];
	double r=0;
	for(int i=2;i<=size;i++){
		if(disPointPoint(o,u[i])<=r) continue;
		o=(u[i]+u[1])/2;
		r=disPointPoint(u[i],u[1])/2;
		for(int j=2;j<i;j++){
			if(disPointPoint(u[j],o)<=r) continue;
			o=(u[i]+u[j])/2;
			r=disPointPoint(u[i],u[j])/2;
			for(int k=1;k<j;k++){
				if(disPointPoint(u[k],o)<=r) continue;
				circle c=concyclic(u[i],u[j],u[k]);
				o=c.cc;r=c.radius;
			}
		}
	}
	circle c;
	c.cc=o;c.radius=r;
	return c;
}

凸包

圈奶牛
Andrew法
手写栈,先求求下凸包,后求上凸包

int stk[N << 1]; // 记得开双倍
vector<Point> andrew(Point* poly,int n) {
    sort(poly + 1, poly + n + 1, [](Point u, Point v) {return (u.x == v.x ? u.y > v.y : u.x < v.x);});
    int top = 1;
    for (int i = 1; i <= n; i++) {
        while (top >= 3 && ((poly[stk[top - 1]] - poly[stk[top - 2]]) ^ (poly[i] - poly[stk[top - 1]])) < 0) {
            top--;
        }
        stk[top++] = i;
    }
    for (int i = n; i >= 1; i--) {
        while (top >= 3 && ((poly[stk[top - 1]] - poly[stk[top - 2]]) ^ (poly[i] - poly[stk[top - 1]])) < 0) {
            top--;
        }
        stk[top++] = i;
    }
    vector<Point> tmp;
    for (int i = 1; i < top - 1; i++) {
        tmp.push_back(poly[stk[i]]);
    }
    return tmp;
}
github