ACM计算几何问题

博客总结了计算几何相关内容,包括矢量概念、加减法、点乘、叉乘,介绍了叉乘在判断向量方向、求三角形面积等方面的应用,还提及皮克定理计算多边形面积,以及判断两线段相交、点是否在线段上、折线段拐向等算法,适用于ACM比赛。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近看到关于计算几何的东西,总结以下
宝贝模板
简单概述
矢量的概念:

如果一条线段的端点是有次序之分的,我们把这种线段成为有向线段(directed segment)。

如果有向线段p1,p2的起点p1在坐标原点,我们可以把它称为矢量(vector) p2。
矢量矢量加减法:

设二维矢量P = ( x1, y1 ),Q = ( x2 , y2 )。

则矢量加法定义为: P + Q = ( x1 + x2 , y1 + y2 ),同样的,矢量减法定义为: P - Q = ( x1 - x2 , y1 - y2 )。

矢量的点乘:

P=(x1, y1),Q=(x2, y2)。则在这里插入图片描述

点乘法可以看成一个向量在另一个向量上的投影与另一个向量的模长相乘。

向量的叉乘

设矢量P = ( x1, y1 ),Q = ( x2, y2 )在这里插入图片描述

则矢量叉积定义为由(0,0)、p1、p2和 p1+p2 所组成的平行四边形的带符号的面积。

感兴趣的可以去百度一下右手定则

其结果是一个标量。

其绝对值等于在这里插入图片描述

使用向量的叉乘可以判断向量的方向。

若 P × Q > 0 , 则P在Q的顺时针方向。

若 P × Q < 0 , 则P在Q的逆时针方向。

若 P × Q = 0 , 则P与Q共线,但可能同向也可能反向。

当然叉积还有一个使用频率很高的用处,即求三角形面积。

注意在凸包问题上,只有凸包会求面积和周长,求点集的面积和周长没有意义

eg p1183
题目描述

给出一个简单多边形(没有缺口),它的边要么是垂直的,要么是水平的。要求计算多边形的面积。

多边形被放置在一个X-Y的卡笛尔平面上,它所有的边都平行于两条坐标轴之一。然后按逆时针方向给出各顶点的坐标值。所有的坐标值都是整数(因此多边形的面积也为整数)。
输入输出格式
输入格式:

输入文件第一行给出多边形的顶点数n(n≤100)。接下来的几行每行给出多边形一个顶点的坐标值X和Y(都为整数并且用空格隔开)。顶点按逆时针方向逐个给出。并且多边形的每一个顶点的坐标值-200≤x,y≤200。多边形最后是靠从最后一个顶点到第一个顶点画一条边来封闭的。
输出格式:

输出文件仅有一行包含一个整数,表示多边形的面积。

#include <bits/stdc++.h>
using namespace std;
int a[105],b[105];
int main(){
    int n,ans = 0;
    scanf("%d",&n);
    for(int i = 0;i < n;i++)
        scanf("%d%d",&a[i],&b[i]);
    a[n] = a[0],b[n] = b[0];
    for(int i = 0;i < n;i++)
        ans = ans + (a[i]*b[i+1]-a[i+1]*b[i]);
    printf("%d\n",ans/2);
    return 0;
}

在这里插入图片描述
通过1/2(DE叉乘AD + DA叉乘DB + DB叉乘DC) // 便利每个

(注意严格按照点的逆时针方向来算,不然你怎么都推不出面积)
注意,用叉乘算的三角形DEA + 三角形 DAB的值是负的, 而叉乘算的三角形 DBC的值是正的,等价于去掉了重叠部分。

acm比赛常用叉乘少用点乘

皮克定理:计算多边形面积

用于计算点阵中顶点在格点上的多边形的面积;

公式表达为2S= 2a + b - 2.

S表示多边形的面积,a表示多边形内部的点数,b表示多边形边界上的点数。

假设有一个直角三角形,它的两条直角边上的点数分别为x、y,那么它的斜边上的点数等于gcd(x,y);

poj2954,通过叉积求面积,通过面积在根据匹克定理求点的多少

#include <iostream>
#include <algorithm>
using namespace std;
int main(){
    int x1,y1,x2,y2,x3,y3;
    while(1){
        int ans = 0;
        int mian = 0;
        cin>>x1>>y1>>x2>>y2>>x3>>y3;
        if(!(x1||y1||x2||y2||x3||y3)){
           break; 
        }
        
        mian = abs((x1-x2)*(y3-y2)-(x3-x2)*(y1-y2))+2;
        
        ans = ans + __gcd(abs(x1 - x2),abs(y1 - y2));
        ans = ans + __gcd(abs(x1 - x3),abs(y1 - y3));
        ans = ans + __gcd(abs(x2 - x3),abs(y2 - y3));

        cout<<(mian - ans)/2<<"\n";
    }

    return 0;
}

三点确定外接圆圆心坐标

struct Point {
    double x,y;
    Point(double x = 0, double y = 0):x(x),y(y){}
};
Point Excenter(Point a, Point b, Point c){
    double a1 = b.x - a.x;
    double b1 = b.y - a.y;
    double c1 = (a1*a1 + b1*b1)/2;
    double a2 = c.x - a.x;
    double b2 = c.y - a.y;
    double c2 = (a2*a2 + b2*b2)/2;
    double d = a1*b2 - a2*b1;
    return Point(a.x + (c1*b2 - c2*b1)/d, a.y + (a1*c2 - a2*c1)/d);
}

凸包点一下,转载这的
在这里插入图片描述

const int maxn = 1e3 + 5;
struct Point {
    double x, y;
    Point(double x = 0, double y = 0):x(x),y(y){}
};
typedef Point Vector;
Point lst[maxn];
int stk[maxn], top;
Vector operator - (Point A, Point B){
    return Vector(A.x-B.x, A.y-B.y);
}
int sgn(double x){
    if(fabs(x) < eps)
        return 0;
    if(x < 0)
        return -1;
    return 1;
}
double Cross(Vector v0, Vector v1) {
    return v0.x*v1.y - v1.x*v0.y;
}
double Dis(Point p1, Point p2) { //计算 p1p2的 距离
    return sqrt((p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y));
}
bool cmp(Point p1, Point p2) { //极角排序函数 ,角度相同则距离小的在前面
    int tmp = sgn(Cross(p1 - lst[0], p2 - lst[0]));
    if(tmp > 0)
        return true;
    if(tmp == 0 && Dis(lst[0], p1) < Dis(lst[0], p2))
        return true;
    return false;
}
//点的编号0 ~ n - 1
//返回凸包结果stk[0 ~ top - 1]为凸包的编号
void Graham(int n) {
    int k = 0;
    Point p0;
    p0.x = lst[0].x;
    p0.y = lst[0].y;
    for(int i = 1; i < n; ++i) {
        if( (p0.y > lst[i].y) || ((p0.y == lst[i].y) && (p0.x > lst[i].x)) ) {
            p0.x = lst[i].x;
            p0.y = lst[i].y;
            k = i;
        }
    }
    lst[k] = lst[0];
    lst[0] = p0;
    sort(lst + 1, lst + n, cmp);
    if(n == 1) {
        top = 1;
        stk[0] = 0;
        return ;
    }
    if(n == 2) {
        top = 2;
        stk[0] = 0;
        stk[1] = 1;
        return ;
    }
    stk[0] = 0;
    stk[1] = 1;
    top = 2;
    for(int i = 2; i < n; ++i) {
        while(top > 1 && Cross(lst[stk[top - 1]] - lst[stk[top - 2]], lst[i] - lst[stk[top - 2]]) <= 0)
            --top;
        stk[top] = i;
        ++top;
    }
    return ;
}

判断两线段是否相交点一下
1)快速排斥试验

设以线段 P1P2 为对角线的矩形为 R,设以线段 Q1Q2 为对角线的矩形为 T,若 R、T 不相交,则两线段不可能相交

假设 P1 = (x1, y1), P2 = (x2, y2), Q1 = (x3, y3), Q2 = (x4, y4),设矩形 R 的 x 坐标的最小边界为 minRX = min(x1, x2),以此类推,将矩形表示为 R = (minRX, minRY, maxRX, maxRY) 的形式,若两矩形相交,则相交的部分构成了一个新的矩形 F,如图所示,我们可以知道 F 的 minFX = max(minRX, minTX), minFY = max(minRY, minTY), maxFX = min(maxRX, maxTX), maxFY = min(maxRY, maxTX),得到 F 的各个值之后,只要判断矩形 F 是否成立就知道 R 和 T 到底有没有相交了,若 minFX > maxFX 或 minFY > maxFy 则 F 无法构成,RT不相交,否则 RT相交
在这里插入图片描述
2)跨立试验点一下,从这复制的

若 P1P2 跨立 Q1Q2,则 P1,P2 分别在 Q1Q2 所在直线的两端,则有 (P1 - Q1)(Q2 - Q1) * (Q2 - Q1)(P2 - Q1) > 0,当 (P1 - Q1)*(Q2 - Q1) = 0 时,说明 (P1 - Q1) 与 (Q2 - Q1) 共线,但由于已经经过快速排斥试验,所以 Q1 必为 P1P2 与 Q1Q2 的交点,依然满足线段相交的条件,则跨立试验可改为:

当 (P1 - Q1)(Q2 - Q1) * (Q2 - Q1)(P2 - Q1) >= 0,则 P1P2 跨立 Q1Q2,当 Q1Q2 也跨立 P1P2 的时候,则 P1P2 相交

(注意式子中被隔开的 * 代表两个叉积的值的相乘,而另外的两个 * 则代表计算矢量叉积)
在这里插入图片描述

/***************************************线段相交模板****************************************/
struct Point{//点
       double x,y;
       Point(){}
       Point(int a,int b){
              x=a;
              y=b;
       }
       void input(){//定义输入函数方便用的时候
              scanf("%lf%lf",&x,&y);
       }
};
struct Line{//线段
       Point a,b;
       Line(){}
       Line(Point x,Point y){
              a=x;
              b=y;
       }
       void input(){
              a.input();
              b.input();
       }
};
bool judge(Point &a,Point &b,Point &c,Point &d)
{
       /*
       快速排斥:
       两个线段为对角线组成的矩形,如果这两个矩形没有重叠的部分,那么两条线段是不可能出现重叠的
       */
       if(!(min(a.x,b.x)<=max(c.x,d.x) && min(c.y,d.y)<=max(a.y,b.y)&&min(c.x,d.x)<=max(a.x,b.x) && min(a.y,b.y)<=max(c.y,d.y)))//这里的确如此,这一步是判定两矩形是否相交
       //1.线段ab的低点低于cd的最高点(可能重合) 2.cd的最左端小于ab的最右端(可能重合)
       //3.cd的最低点低于ab的最高点(加上条件1,两线段在竖直方向上重合) 4.ab的最左端小于cd的最右端(加上条件2,两直线在水平方向上重合)
       //综上4个条件,两条线段组成的矩形是重合的
       /*特别要注意一个矩形含于另一个矩形之内的情况*/
       return false;
       /*
       跨立实验:
       如果两条线段相交,那么必须跨立,就是以一条线段为标准,另一条线段的两端点一定在这条线段的两段
       也就是说a b两点在线段cd的两端,c d两点在线段ab的两端
       */
    double u,v,w,z;//分别记录两个向量
    u=(c.x-a.x)*(b.y-a.y)-(b.x-a.x)*(c.y-a.y);
       v=(d.x-a.x)*(b.y-a.y)-(b.x-a.x)*(d.y-a.y);
       w=(a.x-c.x)*(d.y-c.y)-(d.x-c.x)*(a.y-c.y);
       z=(b.x-c.x)*(d.y-c.y)-(d.x-c.x)*(b.y-c.y);
       return (u*v<=0.00000001 && w*z<=0.00000001);
}

/***************************************线段相交模板****************************************/

判断点是否在线段上

设点 Q = (x, y), P1 = (x1, y1), P2 = (x2, y2),若 (Q - P1)*(P2 - P1) = 0 且 min(x1, x2) <= x <= max(x1, x2) && min(y1, y2) <= y <= max(y1, y2),则点 Q 在线段 P1P2 上

折线段的拐向判断

如图,假设有折线段 p0p1p2 ,要确定 p1p2 相对于 p0p1 是向左拐还是向右拐,可以通过计算(p2 - p0)*(p1 - p0) 的符号来确定折线的拐向(点 p2 - p0 实际上就是向量 p2,但这里要注意线段和矢量的区别)
在这里插入图片描述
POJ - 1410 直线与矩形相交

#include <cstdio>
#include <cmath>
#include <iostream>
using namespace std;
const double eps = 1E-8;
const double pi = acos(-1.0);

int sgn(double x){
    if(fabs(x) < eps)return 0;
    if(x < 0)return - 1;
    else return 1;
}

struct Point{
    double x,y;
    Point(){}
    Point(double _x,double _y){
        x = _x;
        y = _y;
    }
    bool operator == (Point b)const{
        return sgn(x - b.x) == 0 && sgn(y - b.y) == 0;
    }
    Point operator -(const Point &b)const{
        return Point(x - b.x,y - b.y);
    }
    Point operator +(const Point &b)const{
        return Point(x+b.x,y+b.y);
    }
    double operator ^(const Point &b)const{
        return x*b.y - y*b.x;
    }

    //点积
    double operator *(const Point &b)const{
        return x*b.x + y*b.y;
    }
    void input(){
        scanf("%lf%lf",&x,&y);
    }
    void output(){
        printf("%.2f %.2f\n",x,y);
    }
};

struct Line{
    Point s,e;
    Line(){}
    Line(Point _s,Point _e){
        s = _s;
        e = _e;
    }
    bool operator ==(Line v){
        return (s == v.s)&&(e == v.e);
    }
    //根据一个点和倾斜角 angle 确定直线,0<=angle<pi
    Line(Point p,double angle){
        s = p;
        if(sgn(angle - pi/2) == 0){
            e = (s + Point(0,1));
        }
        else{
            e = (s + Point(1,tan(angle)));
        }
    }
    //ax+by+c=0
    Line(double a,double b,double c){
        if(sgn(a) == 0){
            s = Point(0, - c/b);
            e = Point(1, - c/b);
        }
        else if(sgn(b) == 0){
            s = Point( - c/a,0);
            e = Point( - c/a,1);
        }
        else{
            s = Point(0, - c/b);
            e = Point(1,( - c - a)/b);
        }
    }

    bool parallel(Line v){
        return sgn((e - s)^(v.e - v.s)) == 0;
    }
    int relation(Point p){
        int c = sgn((p - s)^(e - s));
        if(c < 0)return 1;
        else if(c > 0)return 2;
        else return 3;
    }

    int linecrossseg(Line v){
        int d1 = sgn((e - s)^(v.s - s));
        int d2 = sgn((e - s)^(v.e - s));
        if((d1^d2)== - 2) return 2;
        return (d1==0||d2==0);
    }

    int linecrossline(Line v){
        if((*this).parallel(v))
            return v.relation(s)==3;
        return 2;
    }

    Point crosspoint(Line v){
        double a1 = (v.e - v.s)^(s - v.s);
        double a2 = (v.e - v.s)^(e - v.s);
        return Point((s.x*a2 - e.x*a1)/(a2 - a1),(s.y*a2 - e.y*a1)/(a2 - a1));
    }
    int segcrossseg(Line v){
        int d1 = sgn((e - s)^(v.s - s));
        int d2 = sgn((e - s)^(v.e - s));
        int d3 = sgn((v.e - v.s)^(s - v.s));
        int d4 = sgn((v.e - v.s)^(e - v.s));
        if( (d1^d2)== - 2 && (d3^d4)== - 2 )return 2;
        return (d1==0 && sgn((v.s - s)*(v.s - e))<=0) ||
            (d2==0 && sgn((v.e - s)*(v.e - e))<=0) ||
            (d3==0 && sgn((s - v.s)*(s - v.e))<=0) ||
            (d4==0 && sgn((e - v.s)*(e - v.e))<=0);
    }
};

//判断点在线段上
//判断点在线段上
bool OnSeg(Point P,Line L)
{
    return
    sgn((L.s-P)^(L.e-P)) == 0 &&
    sgn((P.x - L.s.x) * (P.x - L.e.x)) <= 0 &&
    sgn((P.y - L.s.y) * (P.y - L.e.y)) <= 0;
}

//判断点在凸多边形内
//点形成一个凸包,而且按逆时针排序(如果是顺时针把里面的<0改为>0)
//点的编号:0~n-1
//返回值:
//-1:点在凸多边形外
//0:点在凸多边形边界上
//1:点在凸多边形内
int inConvexPoly(Point a,Point p[],int n)
{
    for(int i = 0;i < n;i++)
    {
        if(sgn((p[i]-a)^(p[(i+1)%n]-a)) < 0)return -1;
        else if(OnSeg(a,Line(p[i],p[(i+1)%n])))return 0;
    }
    return 1;
}
int main(){
    int t;
    double x1,y1,x2,y2;
    scanf("%d",&t);
    while(t--){
        int xx,yy;
        scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
        xx = x1;
        yy = y1;
        Line line = Line(Point(x1,y1),Point(x2,y2));
        scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
        if(x1 > x2)swap(x1,x2);
        if(y1 > y2)swap(y1,y2);
        Point p[10];
        p[0] = Point(x1,y1);
        p[1] = Point(x2,y1);
        p[2] = Point(x2,y2);
        p[3] = Point(x1,y2);

        Line a = Line(p[0],p[1]);
        Line b = Line(p[0],p[3]);
        Line c = Line(p[1],p[2]);
        Line d = Line(p[2],p[3]);

        if(line.segcrossseg(a) || line.segcrossseg(b) || line.segcrossseg(c) || line.segcrossseg(d)){
            printf("T\n");
        }
        else{
            if(inConvexPoly(line.s,p,4) >= 0 || inConvexPoly(line.e,p,4) >= 0){
                printf("T\n");
            }
            else{
                printf("F\n");
            }
        }
    }
    return 0;
}
ACM 很全的计算几何模板 基础部分 1.几何公式 5 1.1三角形 5 1.2四边形 5 1.3正n边形 5 1.4圆 5 1.5棱柱 6 1.6棱锥 6 1.7棱台 6 1.8圆柱 6 1.9圆锥 6 1.10圆台 7 1.11球 7 1.12球台 7 1.13球扇形 7 2.直线与线段 7 2.0预备函数 7 2.1判三点是否共线 8 2.2判点是否在线段上 9 2.3判断两点在线段的同一侧 9 2.4判断两点是否在线段的异侧 9 2.5求点关于直线的对称点 10 2.7判断两线段是否相交 10 2.7.1常用版 10 2.7.2不常用版 11 2.8 求两条直线的交点 11 2.9点到直线的最近距离 12 2.10点到线段的最近距离 12 3.多边形 12 3.0 预备浮点函数 12 3.1判定是否是凸多边形 13 3.2判定点是否在多边形内 14 3.3 判定一条线段是否在一个任意多边形内 15 4. 三角形 16 4.0预备函数 16 4.1求三角形的外心 17 4.2求三角形内心 17 4.3求三角形垂心 17 5. 圆 18 5.0预备函数 18 5.1判定直线是否与圆相交 19 5.2判定线段与圆相交 19 5.3判圆和圆相交 19 5.4计算圆上到点p最近点 19 5.5计算直线与圆的交点 20 5.6计算两个圆的交点 20 6. 球面 21 6.0给出地球经度纬度,计算圆心角 21 6.1已知经纬度,计算地球上两点直线距离 21 6.2已知经纬度,计算地球上两点球面距离 21 7. 三维几何的若干模板 22 7.0预备函数 22 7.1判定三点是否共线 23 7.2判定四点是否共面 23 7.1判定点是否在线段上 23 7.2判断点是否在空间三角形上 24 7.3判断两点是否在线段同侧 24 7.4判断两点是否在线段异侧 25 7.5判断两点是否在平面同侧 25 7.6判断两点是否在平面异侧 25 7.7判断两空间直线是否平行 25 7.8判断两平面是否平行 26 7.9判断直线是否与平面平行 26 7.10判断两直线是否垂直 26 7.11判断两平面是否垂直 26 7.12判断两条空间线段是否相交 27 7.13判断线段是否与空间三角形相交 27 7.14计算两条直线的交点 28 7.15计算直线与平面的交点 28 7.16计算两平面的交线 29 7.17点到直线的距离 29 7.18 计算点到平面的距离 29 7.19计算直线到直线的距离 30 7.20空间两直线夹角的cos值 30 7.21两平面夹角的cos值 30 7.22直线与平面夹角sin值 31 1.最远曼哈顿距离 31 2. 最近点对 32 3. 最近点对 34 4. 最小包围圆 36 5. 求两个圆的交点 39 6. 求三角形外接圆圆心 40 7. 求凸包 42 8.凸包卡壳旋转求出所有对踵点、最远点对 44 9. 凸包+旋转卡壳求平面面积最大三角 47 10. Pick定理 50 11. 求多边形面积和重心 51 12. 判断一个简单多边形是否有核 52 13. 模拟退火 54 14. 六边形坐标系 56 15. 用一个给定半径的圆覆盖最多的点 60 16. 不等大的圆的圆弧表示 62 17. 矩形面积并 62 18. 矩形的周长并 66 19. 最近圆对 70 20. 求两个圆的面积交 74
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值