计算几何
cpp
namespace Geometry{
const double PI=acos(-1);
const double eps=1e-12;
struct Point{//点坐标
double x,y;
Point(double x=0,double y=0):x(x),y(y) {}
//可用Point a(1,2);语句直接将值设置为x=1,y=2
//如果未设定初值则直接设置为x=0,y=0
bool operator==(const Point a) const{
return (fabs(x-a.x)<=eps&&fabs(y-a.y)<=eps);
}
bool operator<(const Point &b) const{//以横纵坐标从小到大排序
return x == b.x ? y < b.y : x < b.x;
}
};
typedef Point Vector;//向量
//简化向量运算
Vector operator +(Vector A,Vector B){
return Vector(A.x+B.x,A.y+B.y);
}
Vector operator -(Vector A,Vector 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);
}
int sign(double x){//符号函数:精度为eps
if(fabs(x)<eps) return 0;//x为0
if(x<0) return -1;//x为负数
return 1;//x为正数
}
int cmp(double x,double y){//比较函数:精度为eps
if(fabs(x-y)<eps) return 0;//x与y相等
if(x<y) return -1;//x小于y
return 1;//x大于y
}
double dot(Point a,Point b){//向量点积
return a.x*b.x+a.y*b.y;
}
double cross(Point a,Point b){//向量叉积
return a.x*b.y-b.x*a.y;
}
double get_dist(Point a,Point b){//求两点间距离
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double get_length(Point a){//向量模长(线段长度)
return sqrt(dot(a,a));
}
double get_angle(Point a,Point b){//求A->B的有向角
return acos(dot(a,b)/get_length(a)/get_length(b));
}
double area(Point a,Point b,Point c){//A为顶点,向量AB与向量AC的叉积,即三角形ABC面积的2倍(有向)
return cross(b-a,c-a);
}
Point rotate(Point a,double angle){
return Point(a.x*cos(angle)+a.y*sin(angle),-a.x*sin(angle)+a.y*cos(angle));
}
Point get_line_intersection(Point p,Vector v,Point q,Vector w){//两直线的交点
//使用前提,直线必须有交点
//cross(v, w) == 0则两直线平行或者重合
Vector u=p-q;
double t=cross(w,u)/cross(v,w);
return p+v*t;
}
//若判断线段/射线是否相交 还需判断交点是否在线段/射线上
double distance_to_line(Point p,Point a,Point b){//点p到直线ab的距离
Vector v1=b-a,v2=p-a;
return fabs(cross(v1,v2)/get_length(v1));
}
double distance_to_segment(Point p,Point a,Point b){//点p到线段ab的距离
if(a==b) return get_length(p-a);
Vector v1=b-a,v2=p-a,v3=p-b;
if(sign(dot(v1,v2))<0) return get_length(v2);
if(sign(dot(v1,v3))>0) return get_length(v3);
return distance_to_line(p,a,b);
}
Point get_line_projection(Point p,Point a,Point b){//点p在直线ab上的投影
Vector v=b-a;
return a+v*(dot(v,p-a)/dot(v,v));
}
bool on_segment(Point p,Point a,Point b){//点p是否在线段ab上
return sign(cross(p-a,p-b))==0&&sign(dot(p-a,p-b))<=0;
}
bool segment_intersection(Point a1,Point a2,Point b1,Point b2){// 判断两个线段a1a2与b1b2是否相交
//在同一直线上但不重合的两线段会错判为相交
double c1=cross(a2-a1,b1-a1),c2=cross(a2-a1,b2-a1);
double c3=cross(b2-b1,a2-b1),c4=cross(b2-b1,a1-b1);
return sign(c1)*sign(c2)<=0&&sign(c3)*sign(c4)<=0;
}
double polygon_area(Point p[],int n){//求多边形面积(不一定是凸多边形)
double s=0;
for(int i=1;i+1<n;i++)
s+=cross(p[i]-p[0],p[i+1]-p[i]);
return s/2;
}
bool in_line(Point p1,Point p2,Point p3){ //判断三点是否共线
double res=(p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y);
if(fabs(res)<=eps) return 0;//共线
return 1;//不共线
}
Point get_circle_center(Point a,Point b,Point c){//根据三点求圆的圆心坐标
double fm1=2*(a.y-c.y)*(a.x-b.x)-2*(a.y-b.y)*(a.x-c.x);
double fm2=2*(a.y-b.y)*(a.x-c.x)-2*(a.y-c.y)*(a.x-b.x);
double x,y;
if(fm1==0||fm2==0)//三点共线
{
x=y=1e18;
return {x,y};
//if(x==y&&abs(x-1e18)<eps) 三点共线
}
double fz1=a.x*a.x-b.x*b.x+a.y*a.y-b.y*b.y;
double fz2=a.x*a.x-c.x*c.x+a.y*a.y-c.y*c.y;
x=(fz1*(a.y-c.y)-fz2*(a.y-b.y))/fm1;
y=(fz1*(a.x-c.x)-fz2*(a.x-b.x))/fm2;
return {x,y};
}
int stk[N];
Point q[N];
bool used[N];
int top;
void andrew(int n){//求凸包
sort(q,q+n);//以横纵坐标从小到大排序
for(int i=0;i<n;i++)
{
while(top>=2&&sign(area(q[stk[top-1]],q[stk[top]],q[i]))<=0)//
//不带等号:可以求出所有点都在一条直线上的情况
{
if(sign(area(q[stk[top-1]],q[stk[top]],q[i]))<0)
used[stk[top--]]=false;
else top--;
}
stk[++top]=i;
used[i]=true;
}
used[0]=false;
for(int i=n-1;i>=0;i--)
{
if(used[i]) continue;
while(top>=2&&sign(area(q[stk[top-1]],q[stk[top]],q[i]))<=0)
top--;
stk[++top]=i;
}
//凸包上的点为1~top-1,q[top]与q[1]为同一点
//该遍历为逆时针顺序!!!
// for (int i = 1; i <= top; i ++ )
// cout << stk[i] << ' ';
// cout<<endl;
}
// -------------------求凸包直径--------------------------------
int rotating_calipers(int n){//旋转卡壳求凸包直径平方
if(top<=2)return get_dist(q[0],q[n-1]);
int res=0;
vector<Point> tmp;
for(int i=1;i<=top;i++)tmp.push_back(q[stk[i]]);
top--;
for(int i=0,j=2;i<top;i++){
auto d=tmp[i],e=tmp[i+1];
while(area(d,e,tmp[j])<area(d,e,tmp[j+1]))j=(j+1)%top;
res=max({res,get_dist(d,tmp[j]),get_dist(e,tmp[j])});
}
return res;
}
//-------求最小矩形覆盖-------------------
Vector norm(Vector a){//法向量
return a/get_length(a);
}
double get_project(Point a, Point b, Point c){
return dot(b - a, c - a) / get_length(b - a);
}
double min_area=1e20;//最小面积
Point ans[4];//矩形四个顶点
void rotating_calipers(int n){//旋转卡壳求最小矩形覆盖
vector<Point> tmp;
for(int i=1;i<=top;i++)tmp.push_back(q[stk[i]]);
top--;
for(int i=0,a=2,b=1,c=2;i<top;i++){
auto d=tmp[i],e=tmp[i+1];
while(cmp(area(d,e,tmp[a]),area(d,e,tmp[a+1]))<0)a=(a+1)%top;
while(cmp(get_project(d,e,tmp[b]),get_project(d,e,tmp[b+1]))<0)b=(b+1)%top;
if(!i)c=a;
while(cmp(get_project(d,e,tmp[c]),get_project(d,e,tmp[c+1]))>0)c=(c+1)%top;
auto x=tmp[a],y=tmp[b],z=tmp[c];
auto h=area(d,e,x)/get_length(e-d);
auto w=dot(y-z,e-d)/get_length(e-d);
if(h*w<min_area){
min_area=h*w;
ans[0] = d + norm(e - d) * get_project(d, e, y);
ans[3] = d + norm(e - d) * get_project(d, e, z);
auto u = norm(rotate(e - d, -PI / 2));
ans[1] = ans[0] + u * h;
ans[2] = ans[3] + u * h;
}
}
}
int cnt;
struct Line{//存储凸包上每条线段的起点和终点
Point st,ed;
}line[N];
Point pg[N],ans[N];//
int l[N];
double line_get_angle(const Line& a){
return atan2(a.ed.y-a.st.y,a.ed.x-a.st.x);
}
bool line_cmp(const Line& a, const Line& b){
double A=line_get_angle(a),B=line_get_angle(b);
if (!cmp(A,B)) return area(a.st,a.ed,b.ed)<0;
return A<B;
}
Point get_line_intersection(Line a,Line b){
return get_line_intersection(a.st,a.ed-a.st,b.st,b.ed-b.st);
}
bool on_right(Line& a, Line& b, Line& c){//判断bc的交点是否在a的右侧
auto o = get_line_intersection(b,c);
return sign(area(a.st,a.ed,o))<=0;
//需要保留经过半平面交的所有直线的时候,将该处等号去掉
}
double half_plane_intersection(){
sort(line,line+cnt,line_cmp);//对每条直线按照极坐标进行排序
int hh=0,tt=-1;//定义双端队列对头和队尾元素
for(int i=0;i<cnt;i++)//遍历每一条直线
{
if (i&&!cmp(line_get_angle(line[i]),line_get_angle(line[i-1]))) continue;//当两直线极坐标角度相同
while(hh+1<=tt&&on_right(line[i],line[l[tt-1]],line[l[tt]])) tt--;
//如果line[l[tt-1]]和line[l[tt]]的交点在line[i]的右侧,删除队尾元素
while(hh+1<=tt&&on_right(line[i],line[l[hh]],line[l[hh+1]])) hh++;
//如果line[l[hh]]和line[l[hh+1]]的交点在line[i]的右侧,删除队头元素
l[++tt]=i;//将该直线加入队尾
}
//判断一下第一条直线与最后一条直线
while (hh+1<=tt&&on_right(line[l[hh]],line[l[tt-1]],line[l[tt]])) tt--;//队尾更新队头
while (hh+1<=tt&&on_right(line[l[tt]],line[l[hh]],line[l[hh+1]])) hh++;//队头更新队尾
l[++tt]=l[hh];//将第一条直线再次加入双端队列末尾
//计算半平面交交点
int k=0;
for (int i=hh;i<tt;i++) //对于半平面交上的直线两两之间求出交点
ans[k++]=get_line_intersection(line[l[i]],line[l[i+1]]);
//计算半平面交面积
return polygon_area(ans,k);
}
Point po[N];
struct Circle{
Point p;
double r;
};
pair<Point,Point> get_line(Point a,Point b){//找到与向量ab垂直的向量的起点与终点
return {(a+b)/2,rotate(b-a,PI/2)};
}
Circle get_circle(Point a,Point b,Point c){//根据不共线的三点求出圆的圆心坐标和半径
auto u=get_line(a,b),v=get_line(a,c);//求出垂直于ab和ac的向量
auto p=get_line_intersection(u.first,u.second,v.first,v.second);//两向量交点即为所求圆的圆心
return {p,get_dist(p,a)};
}
Circle get_circle(Point a,Point b,Point c){//根据不共线的三点求出圆的圆心坐标和半径
Point p=get_circle_center(a,b,c);
double r=get_dist(p,a);
return {p,r};
}
void smallest_circle(int n)
{
random_shuffle(po,po+n);
Circle c={po[0],0};//最开始时直接将第一个点看作圆心,半径为0
for(int i=1;i<n;i++)
{
if(cmp(c.r,get_dist(c.p,po[i]))<0)//如果第i个点不在前i-1个点的最小圆覆盖内,则进入下一重循环
{
c={po[i],0};//将第i个点当作圆心,半径为0,重新确定圆
for(int j=0;j<i;j++)
{
if(cmp(c.r,get_dist(c.p,po[j]))<0)//
{
c={(po[i]+po[j])/2,get_dist(po[i],po[j])/2};
for(int k=0;k<j;k++)
if(cmp(c.r,get_dist(c.p,po[k]))<0)//确定了最小圆覆盖上的三个点,直接求出圆
c=get_circle(po[i],po[j],po[k]);
}
}
}
}
printf("%.10lf\n",c.r);//输出圆的半径
printf("%.10lf %.10lf\n",c.p.x,c.p.y);//输出圆心坐标
}
//------------求圆与三角形的面积并---------------------
Point q[N],r;
double R;
Vector norm(Vector a){//法向量
return a/get_length(a);
}
double get_circle_line_intersection(Point a, Point b, Point& pa, Point& pb)
{
auto e = get_line_intersection(a, b - a,r, rotate(b - a, PI / 2));
auto mind = get_dist(r, e);
if (!on_segment(e, a, b)) mind = min(get_dist(r, a), get_dist(r, b));
if (cmp(R, mind) <= 0) return mind;
auto len = sqrt(R * R - get_dist(r, e) * get_dist(r, e));
pa = e + norm(a - b) * len;
pb = e + norm(b - a) * len;
return mind;
}
double get_sector(Point a, Point b)
{
auto angle = acos((dot(a, b)) / get_length(a) / get_length(b));
if (sign(cross(a, b)) < 0) angle = -angle;
return R * R * angle / 2;
}
double get_circle_triangle_area(Point a, Point b)
{
auto da = get_dist(r, a), db = get_dist(r, b);
if (cmp(R, da) >= 0 && cmp(R, db) >= 0) return cross(a, b) / 2;
if (!sign(cross(a, b))) return 0;
Point pa, pb;
auto mind = get_circle_line_intersection(a, b, pa, pb);
if (cmp(R, mind) <= 0) return get_sector(a, b);
if (cmp(R, da) >= 0) return cross(a, pb) / 2 + get_sector(pb, b);
if (cmp(R, db) >= 0) return get_sector(a, pa) + cross(pa, b) / 2;
return get_sector(a, pa) + cross(pa, pb) / 2 + get_sector(pb, b);
}
double work(int n)
{
double res = 0;
for (int i = 0; i < n; i ++ )
res += get_circle_triangle_area(q[i], q[(i + 1) % n]);
return fabs(res);
}
}
using namespace Geometry;
void solve1()//求凸包
{
scanf("%d",&n);//点个数
for(int i=0;i<n;i++) scanf("%lf%lf",&q[i].x,&q[i].y);//输入点坐标
//下标从0开始,且求凸包之后不保留原下标顺序
andrew();//求凸包
double ans=1e9;
printf("%.10lf",ans);
}
void solve2()//求半平面交
{
int n,m;
scanf("%d",&n);
while(n--)
{
scanf("%d",&m);
for(int i=0;i<m;i++) scanf("%lf%lf",&pg[i].x,&pg[i].y);//读入凸包上的所有点
for(int i=0;i<m;i++) line[cnt++]={pg[i],pg[(i+1)%m]};//指定每一条线段的两端点
//pg[(i+1)%m]处理最后一个点与第一个点之间的线段
}
double res=half_plane_intersection();
printf("%.3lf\n",res);
}
void solve3()//求最小圆覆盖
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%lf%lf",&po[i].x,&po[i].y);
random_shuffle(po,po+n);//将所有的点随机化
smallest_circle();
}