题目:http://lightoj.com/volume_showproblem.php?problem=1190

参考链接:https://blog.csdn.net/gkingzheng/article/details/81836308

http://www.mamicode.com/info-detail-1555428.html

一、比如说,我就随便涂了一个多边形和一个点,现在我要给出一种通用的方法来判断这个点是不是在多边形内部(别告诉我用肉眼观察……)。

首先想到的一个解法是从这个点做一条射线,计算它跟多边形边界的交点个数,如果交点个数为奇数,那么点在多边形内部,否则点在多边形外。

这个结论很简单,那它是怎么来的?下面就简单讲解一下。

首先,对于平面内任意闭合曲线,我们都可以直观地认为,曲线把平面分割成了内、外两部分,其中“内”就是我们所谓的多边形区域。

基于这一认识,对于平面内任意一条直线,我们可以得出下面这些结论:

  • 直线穿越多边形边界时,有且只有两种情况:进入多边形或穿出多边形。
  • 在不考虑非欧空间的情况下,直线不可能从内部再次进入多边形,或从外部再次穿出多边形,即连续两次穿越边界的情况必然成对。
  • 直线可以无限延伸,而闭合曲线包围的区域是有限的,因此最后一次穿越多边形边界,一定是穿出多边形,到达外部。

现在回到我们最初的题目。假如我们从一个给定的点做射线,还可以得出下面两条结论:

  • 如果点在多边形内部,射线第一次穿越边界一定是穿出多边形。
  • 如果点在多边形外部,射线第一次穿越边界一定是进入多边形。

把上面这些结论综合起来,我们可以归纳出:

  • 当射线穿越多边形边界的次数为偶数时,所有第偶数次(包括最后一次)穿越都是穿出,因此所有第奇数次(包括第一次)穿越为穿入,由此可推断点在多边形外部。
  • 当射线穿越多边形边界的次数为奇数时,所有第奇数次(包括第一次和最后一次)穿越都是穿出,由此可推断点在多边形内部。

到这里,我们已经了解了这个解法的思路,大家可以试着自己写一个实现出来。关于算法实现中某些具体问题和边界条件的处理,下次接着写,这次画图已经画够了……

后记:给出这个解法后,我简单搜了一下,原来这种算法就叫做射线法(ray casting)或者奇偶规则法(even odd rule),是一种早已被广泛应用的算法。后面还打算介绍另一种通过回转数(winding number,拓扑学的一个概念)解这个问题的思路。

二、射线法在实际应用中的一些问题和解决方案。

  1. 点在多边形的边上

    前面我们讲到,射线法的主要思路就是计算射线穿越多边形边界的次数。那么对于点在多边形的边上这种特殊情况,射线出发的这一次,是否应该算作穿越呢?

    看了上面的图就会发现,不管算不算穿越,都会陷入两难的境地——同样落在多边形边上的点,可能会得到相反的结果。这显然是不正确的,因此对这种特殊情况需要特殊处理。

  2. 点和多边形的顶点重合

    这其实是第一种情况的一个特例。

  3. 射线经过多边形顶点

    射线刚好经过多边形顶点的时候,应该算一次还是两次穿越?这种情况比前两种复杂,也是实现中的难点,后面会讲解它的解决方案。

  4. 射线刚好经过多边形的一条边

    这是上一种情况的特例,也就是说,射线连续经过了多边形的两个相邻顶点。

解决方案:

  1. 判断点是否在线上的方法有很多,比较简单直接的就是计算点与两个多边形顶点的连线斜率是否相等,中学数学都学过。

  2. 点和多边形顶点重合的情况更简单,直接比较点的坐标就行了。

  3. 顶点穿越看似棘手,其实我们换一个角度,思路会大不相同。先来回答一个问题,射线穿越一条线段需要什么前提条件?没错,就是线段两个端点分别在射线两侧。只要想通这一点,顶点穿越就迎刃而解了。这样一来,我们只需要规定被射线穿越的点都算作其中一侧。

    如上图,假如我们规定射线经过的点都属于射线以上的一侧,显然点D和发生顶点穿越的点C都位于射线Y的同一侧,所以射线Y其实并没有穿越CD这条边。而点C和点B则分别位于射线Y的两侧,所以射线Y和BC发生了穿越,由此我们可以断定点Y在多边形内。同理,射线X分别与AD和CD都发生了穿越,因此点X在多边形外,而射线Z没有和多边形发生穿越,点Z位于多边形外。

  4. 解决了第三点,这一点就毫无难度了。根据上面的假设,射线连续经过的两个顶点显然都位于射线以上的一侧,因此这种情况看作没有发生穿越就可以了。由于第三点的解决方案实际上已经覆盖到这种特例,因此不需要再做特别的处理

  5. 代码:
    /*
    射线法:判断一个点是在多边形内部,边上还是在外部,时间复杂度为O(n);
    射线法可以正确用于凹多边形;
    射线法是使用最广泛的算法,这是由于相比较其他算法而言,它不但可以正
    确使用在凹多边形上,而且不需要考虑精度误差问题。该算法思想是从点出
    发向右水平做一条射线,计算该射线与多边形的边的相交点个数,当点不在
    多边形边上时,如果是奇数,那么点就一定在多边形内部,否则,在外部。
    */
    #include <stdio.h>
    #include <algorithm>
    #include <cstring>
    #include <cmath>
    using namespace std;
    const int N = 2010;
    const double eps = 1e-10;
    const int INF = 0x3f3f3f3f;
    //////////////////////////////////////////////////////////////////
    struct point
    {
        double x, y;
        point(double x=0, double y=0) : x(x), y(y){}
        friend point operator - (const point& p1, const point& p2)
        {
            return point(p1.x-p2.x, p1.y-p2.y);
        }
        friend double operator ^ (const point& p1, const point& p2)
        {
            return p1.x*p2.y - p1.y*p2.x;
        }
    };
    //////////////////////////////////////////////////////////////////
    struct Segment
    {
        point s, e;
    };
    //////////////////////////////////////////////////////////////////
    ///判断一个double类型的数是  0  <0  >0;
    int Sign(double x)
    {
        if( fabs(x) < eps )return 0;
        if(x > 0)return 1;
        return -1;
    }
    //////////////////////////////////////////////////////////////////
    ///判断o在ab的哪边;0:o在直线ab上; >0:在左边; <0:在右边;
    double cross(point o, point a, point b)
    {
        return ((a-o)^(b-o));
    }
    //////////////////////////////////////////////////////////////////
    ///已知abc三点在一条直线上,判断点a是否在线段bc之间;<=0:在   >0:不在;
    int Between(point a, point b, point c)
    {
        if(fabs(b.x-c.x) > fabs(b.y-c.y))
            return Sign(min(b.x, c.x)-a.x)*Sign(max(b.x, c.x)-a.x);
        else
            return Sign(min(b.y, c.y)-a.y)*Sign(max(b.y, c.y)-a.y);
    }
    //////////////////////////////////////////////////////////////////
    ///判断点p0和线段S上,<=0:在,1:不在;
    int PointOnSegment(point p0, Segment S)
    {
        if(Sign(cross(S.s, S.e, p0)) == 0)
            return Between(p0, S.s, S.e);
        return 1;
    }
    //////////////////////////////////////////////////////////////////
    ///求线段a和线段b的交点个数;
    int SegmentCross(Segment a, Segment b)
    {
        double x1 = cross(a.s, a.e, b.s);
        double x2 = cross(a.s, a.e, b.e);
        double x3 = cross(b.s, b.e, a.s);
        double x4 = cross(b.s, b.e, a.e);
        if(Sign(x1*x2)<0 && Sign(x3*x4)<0) return 1;
        if((Sign(x1)==0 && Between(b.s, a.s, a.e)<=0) ||
           (Sign(x2)==0 && Between(b.e, a.s, a.e)<=0) ||
           (Sign(x3)==0 && Between(a.s, b.s, b.e)<=0) ||
           (Sign(x4)==0 && Between(a.e, b.s, b.e)<=0))
           return 2;
        return 0;
    }
    //////////////////////////////////////////////////////////////////
    ///判断点p0与含有n个节点的多边形的位置关系,p数组是顶点集合;
    ///返回0:边上或顶点上,    1:外面,   -1:里面;
    int PointInPolygon(point p0, point p[], int n)
    {
        Segment L, S;
        point temp;
        L.s = p0, L.e = point(INF, p0.y);///以p0为起点的射线L;
        int counts = 0;
        p[n] = p[0];
        for(int i=1; i<=n; i++)
        {
            S.s = p[i-1], S.e = p[i];
            if(PointOnSegment(p0, S) <= 0) return 0;
            if(S.s.y == S.e.y) continue;///和射线平行;
            if(S.s.y > S.e.y) temp = S.s;
            else temp = S.e;
            if(PointOnSegment(temp, L) == -1)
                counts ++;
            else if(SegmentCross(L, S) == 1)
                counts ++;
        }
        if(counts%2) return -1;
        return 1;
    }
    //////////////////////////////////////////////////////////////////
    int main()
    {
        point p[N];
        int T, tCase = 1, n, q;
        scanf("%d", &T);
        while(T--)
        {
            scanf("%d", &n);
            for(int i=0; i<n; i++)
                scanf("%lf %lf", &p[i].x, &p[i].y);
            scanf("%d", &q);
            printf("Case %d:\n", tCase++);
            for(int i=1; i<=q; i++)
            {
                int x, y;
                scanf("%d %d", &x, &y);
                int ans = PointInPolygon(point(x, y), p, n);
                if(ans == 1) puts("No");
                else puts("Yes");
            }
        }
        return 0;
    }

射线法(1190 - Sleepwalking )的更多相关文章

  1. LightOj1190 - Sleepwalking(判断点与多边形的位置关系--射线法模板)

    题目链接:http://lightoj.com/volume_showproblem.php?problem=1190 题意:给你一个多边形含有n个点:然后又m个查询,每次判断点(x, y)是否在多边 ...

  2. matlab练习程序(射线法判断点与多边形关系)

    依然是计算几何. 射线法判断点与多边形关系原理如下: 从待判断点引出一条射线,射线与多边形相交,如果交点为偶数,则点不在多边形内,如果交点为奇数,则点在多边形内. 原理虽是这样,有些细节还是要注意一下 ...

  3. Codeforces 375C Circling Round Treasures - 最短路 - 射线法 - 位运算

    You have a map as a rectangle table. Each cell of the table is either an obstacle, or a treasure wit ...

  4. WebGL模型拾取——射线法二

    这篇文章是对射线法raycaster的补充,上一篇文章主要讲的是raycaster射线法拾取模型的原理,而这篇文章着重讲使用射线法要注意的地方.首先我们来看下图. 我来解释一下上图中的originTr ...

  5. WebGL模型拾取——射线法

    今天要把WebGL中一个非常重要的算法记录下来——raycaster射线法拾取模型.首先我们来了解一下为什么要做模型拾取,我们在做webgl场景交互的时候经常要选中场景中的某个模型,比如鼠标拖拽旋转, ...

  6. Revit API射线法读取空间中相交的元素

    Revit API提供根据射线来寻找经过的元素.方法是固定模式,没什么好说.关键代码:doc.FindReferencesWithContextByDirection(ptStart, (ptEnd  ...

  7. 【BZOJ1294】[SCOI2009]围豆豆Bean 射线法+状压DP+SPFA

    [BZOJ1294][SCOI2009]围豆豆Bean Description Input 第一行两个整数N和M,为矩阵的边长. 第二行一个整数D,为豆子的总个数. 第三行包含D个整数V1到VD,分别 ...

  8. SGU 124. Broken line 射线法 eps的精准运用,计算几何 难度:3

    124. Broken line time limit per test: 0.25 sec. memory limit per test: 4096 KB There is a closed bro ...

  9. BZOJ 1656 [Usaco2006 Jan] The Grove 树木:bfs【射线法】

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1656 题意: 给你一个n*m的地图,'.'表示空地,'X'表示树林,'*'表示起点. 所有 ...

随机推荐

  1. nginx的配置与应用

    Nginx在应用程序中主要有以下作用(应用):1.解决跨域.2.请求过滤.3.配置Gzip.4.负载均衡.5.静态资源服务器. Nginx的配置结构 Nginx主要是通过修改配置文件nginx.con ...

  2. python高级编程笔记一 正则表达式

    事例

  3. 基于Redis实现分布式锁

    分布式锁具有的特性: 1.排他性: 文件系统: 数据库:主键 唯一约束 for update 性能较差,容易出现单点故障 锁没有失效时间,容易死锁 缓存Redis:setnx 实现复杂: 存在死锁(或 ...

  4. Jetson TX1刷机

    刷机流程 https://blog.csdn.net/c406495762/article/details/70786700 注意:教程中包含两步,首先安装Ubuntu系统,然后重启安装程序,安装其他 ...

  5. Oracle数据库启动出现ORA-27101错误之ORA-19815处理方式及数据备份

    ORA-27101: sharedmemory realm does not exist之ORA-19815处理 重启数据库(数据库:muphy),登陆是越到错误: ORA-27101: shared ...

  6. 在线SQL

    有时候为了简单测试一下SQL语法,自己机器上没有安装,可使用在线的SQL环境. 我找到了两个: https://rextester.com/l/sql_server_online_compiler ( ...

  7. HTML&CSS_基础03

    一.Meta标签: 1.可以设置网页的关键字 2.用来指定网页描述 3.可以用来网页重定向 具体参数参见:http://www.w3school.com.cn/html5/tag_meta.asp 二 ...

  8. 使用apidoc生成项目文档

    [1]npm install apidoc -g 全局安装apidoc [2]apidoc -v 查看是否安装成功 [3]apidoc.json apidoc的项目级配置文件,它必须位于整个工程目录顶 ...

  9. KEIL之工程单独文件属性修改

    @2019-04-29 [小记] 可以设置文件是否参与编译.内存分配.宏定义等属性设置

  10. [WC2006]水管局长(LCT)

    题目大意: 给定一张图,支持删边,求两点的路径中所有权值的最大值的最小值,貌似很绕的样子 由于有删边,不难想到\(LCT\),又因为\(LCT\)不支持维护图,而且只有删边操作,于是我们考虑时间回溯. ...