题目描述
给定一个简单多边形(边与坐标轴平行,但算法适用于任意简单多边形)及其顶点坐标(按顺序给出,可能是顺时针或逆时针),以及一个待检测的点PPP。要求判断点PPP是否位于多边形内部(包括边界)。输入包含多组数据,每组以顶点个数nnn开始,随后nnn行顶点坐标,最后一行给出点PPP的坐标。输入以n=0n = 0n=0结束。
输出时,若点在多边形内部(含边界),输出T,否则输出F。
输入格式
每组数据第一行为一个整数nnn(n≤1000n \le 1000n≤1000),表示多边形顶点个数。接下来nnn行,每行两个整数xxx和yyy,按顺序给出多边形顶点坐标。随后一行包含两个整数,为待检测点PPP的坐标。输入以n=0n = 0n=0结束。
输出格式
对于每个点,输出一行字符:T表示点在多边形内部(或边界),F表示点在多边形外部。
样例
输入
4 1 1 1 3 3 3 1 2 2 2 12 1 1 1 9 3 9 3 5 5 5 5 9 7 9 7 1 5 1 5 3 3 3 1 4 2 0输出
T F题目分析
本题是经典的点与多边形位置关系判定问题。常用的算法是射线法(奇偶规则):从点PPP向右作水平射线,统计该射线与多边形边的交点数。若交点数为奇数,则点在多边形内部;若为偶数,则在外部。若点恰好在边上,也应判定为内部。
在实现时,需要处理射线与多边形顶点相交的边界情况,以避免重复计数。通常的解决方案是:只统计那些跨越射线水平线且两个端点位于射线水平线两侧(或者其中一个端点在射线上,另一个端点在上方)的边。代码中采用了一种简洁的处理方式:对每条边,先判断射线与边所在线段是否相交(通过方向叉积),然后对顶点相交的情况,只计数那些边的两个端点都在射线水平线上方(即side.start.y >= ray.start.y && side.end.y >= ray.start.y)的情况,从而确保每个顶点只被计数一次。
解题思路
射线法基本原理
从点PPP向右作一条水平射线,与多边形的边求交。若射线穿过多边形边界奇数次,则PPP在多边形内部;否则在外部。
交点判断
对于每条边ABABAB和射线(起点为PPP,方向向右),判断它们是否相交:
- 若射线与边无交点,则跳过。
- 若射线与边相交于边内部(非端点),则计数加一。
- 若射线与边的某个端点相交,则必须统一规则,防止端点被重复计数。代码中采用“只计数边的两个端点都在射线水平线上方”的规则,即只有当边的两个端点的yyy坐标都不小于射线起点的yyy坐标时,才认为该交点有效。
实际上,更常见的做法是:若射线与边的交点恰好是边的上端点,则计数,下端点则不计数。代码中的规则与之等价。
具体步骤
- 读入多边形顶点数组。
- 构造一条水平射线,起点为点PPP,终点为xxx坐标足够大的点(例如2×2 \times2×多边形最右顶点的xxx坐标,确保射线能穿过多边形)。
- 遍历多边形的每条边,调用
segmentsIntersect判断射线与边是否相交。 - 统计相交次数,若为奇数则返回
true,否则false。
边界处理
- 点恰好在边上的情况:由于题目输入满足一定条件(点PPP的坐标均为偶数,多边形边长为偶数等),但算法中未特殊处理,在判断相交时,若点与边共线且点在边上,则方向叉积为000,且点位于边范围内,此时应判定为内部。但代码中对于共线情况直接返回
false,这可能漏掉点在边上的情况。然而根据题目输入的特殊性(点PPP坐标为偶数,多边形边长为偶数,且至少一个顶点坐标为奇数),点PPP不可能落在多边形边上(因为边上的点至少有一个坐标为奇数?或者边长偶数导致边界点坐标奇偶性固定),因此可以安全忽略边界情况。为通用性,也可增加点在边上的显式判断,但代码未做,故保持原样。
复杂度分析
- 每组数据,对每条边进行常数次叉积运算,时间复杂度O(n)O(n)O(n),其中nnn为顶点数(n≤1000n \le 1000n≤1000)。
- 空间复杂度O(n)O(n)O(n),用于存储顶点。
代码实现
// Polygon// UVa ID: 634// Verdict: Accepted// Submission Date: 2016-08-29// UVa Run Time: 0.000s//// 版权所有(C)2016,邱秋。metaphysis # yeah dot net#include<bits/stdc++.h>usingnamespacestd;constintMAX_VERTICES=1100;constintEPSILON=0;structpoint{doublex,y;};structsegment{point start,end;};structpolygon{intvertexNumber;point vertex[MAX_VERTICES];};// 使用叉积来表示线段的相对方向。doubledirection(point a,point b,point c){return(b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);}// 判断多边形的边是否在射线上方。boolisSideAboveRay(segment ray,segment side){returnside.start.y>=ray.start.y&&side.end.y>=ray.start.y;}// 根据设定的交点数规则判断射线与多边形的边是否相交。boolsegmentsIntersect(segment ray,segment side){doubled1,d2,d3,d4;d1=direction(ray.start,ray.end,side.start);d2=direction(ray.start,ray.end,side.end);d3=direction(side.start,side.end,ray.start);d4=direction(side.start,side.end,ray.end);// 跨越式相交。if((d1*d2<0)&&(d3*d4<0))returntrue;// 不相交。if((d1*d2>0)||(d3*d4>0))returnfalse;// 共线,不论线段是否重合,均规定为不相交。if((fabs(d1)<=EPSILON&&fabs(d2)<=EPSILON)||(fabs(d3)<=EPSILON&&fabs(d4)<=EPSILON))returnfalse;// 相交于顶点,判断是否在射线上方。if(fabs(d1)<=EPSILON||fabs(d2)<=EPSILON||fabs(d3)<=EPSILON||fabs(d4)<=EPSILON)returnisSideAboveRay(ray,side);returnfalse;}boolisPointInPolygon(point&p,polygon&pg){// 找到多边形顶点中位于最右边的点的横坐标。doublerightX=pg.vertex[0].x;for(inti=0;i<pg.vertexNumber;i++)if(pg.vertex[i].x>rightX)rightX=pg.vertex[i].x;intnumberOfIntersection=0;segment ray=(segment){p,(point){2*rightX,p.y}};for(inti=0;i<pg.vertexNumber;i++){segment side=(segment){pg.vertex[i],pg.vertex[(i+1)%pg.vertexNumber]};if(segmentsIntersect(ray,side))numberOfIntersection++;}// 测试交点个数奇偶性。return((numberOfIntersection&1)==1);}intmain(){cin.tie(0);cout.tie(0);ios::sync_with_stdio(false);point p;polygon pg;while(cin>>pg.vertexNumber,pg.vertexNumber){for(inti=0;i<pg.vertexNumber;i++)cin>>pg.vertex[i].x>>pg.vertex[i].y;cin>>p.x>>p.y;cout<<(isPointInPolygon(p,pg)?'T':'F')<<'\n';}return0;}总结
本题使用射线法判断点与多边形的位置关系,通过方向叉积判断线段相交,并采用“端点只计上方”的规则避免顶点重复计数。由于输入数据满足特定奇偶性条件,边界情况被简化,使得算法可以稳定运行。时间复杂度为O(n)O(n)O(n),空间复杂度O(n)O(n)O(n),适合n≤1000n \le 1000n≤1000的数据规模。此解法是计算几何中点与多边形关系判定问题的经典实现,掌握该方法对解决类似几何问题很有帮助。