ACM计算几何问题

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

如果一条线段的端点是有次序之分的,我们把这种线段成为有向线段(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;
}
  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值