POJ 3335 半平面交与多边形的核

本文介绍了计算几何中的半平面交概念及其在求解多边形核问题上的应用。通过详细阐述算法步骤,包括逆时针/顺时针排序点,维护点集合kernel,判断点相对于直线的位置,以及通过半平面交求核的边界点,揭示了如何找到多边形的核。虽然算法复杂度为O(N^2),但提供了直观的理解,并提及存在O(NlogN)的优化方法。

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

半平面,指的就是一条直线把一个平面分成了两个半平面…… 如果有好几条这样的直线,就会有很多个半平面,我们有的时候会求他们的交。


半平面交一个很有用的用处是求多边形的核。所谓多边形的核,就是指多边形中的一块区域,在这个区域里面放一个灯泡,这个灯泡转一周,能照亮这个多边形里所有的地方。稍微想想就会知道,凸多边形一定是有核的,而且就是它本身,但是凹多边形就未必有核了。比如这个多边形




蓝色的区域就是它的核,但是如果你站在淡黄色点的话,叉子的那一部分你就看不到了。


怎么求一个多边形的核呢?有这样一个算法:


1. 将所有点逆时针/顺时针排序,到底是逆时针还是顺时针很关键,影响到后面符号的判断。

2. 维护一个点集合kernel,这个点集合所包含的点就是这个多边形的核;

3. 对于原多边形的每条边构成的直线line,扫一下所有的点,如果点i就在对于这条线而言的多边形内部,那么点i直接就加入kernel集合;

否则,考虑点i旁边的两个点i+1, i-1, 对每个点判断,如果它在对现在直线line而言的多边形内部,那么一个在外部的点i和一个在内部的点j必然会与现在这条直线相交,相交的点加入kernel;

这里,如何判断“一个点对一条直线而言,是在多边形的内部还是外部”呢? 

对于P1P2两点构成的直线,我们用这个函数将其写成Ax + By + C = 0的形式:

void Get_equation(Point p1,Point p2,double &a,double &b,double &c)
{
<span style="white-space:pre">	</span>a = p2.y - p1.y;
<span style="white-space:pre">	</span>b = p1.x - p2.x;
<span style="white-space:pre">	</span>c = p2.x*p1.y - p1.x*p2.y;
}

然后,将当前点代入Ax + By + C,如果是顺时针排的,则如果结果大于0,就是在内部,如果是逆时针排的,结果小于0即是在内部。

4. 最后留在kernel集合内的点,就是我们要求的核的边界点。


这个算法的道理其实就是半平面交,因为对于每一条边而言,它的“多边形内部”就是一个半平面,比如一个正方形ABCD,对AB边所在直线而言,只有一边是在多边形内的,当然也只有从这个区域才能看见AB边。所以算法实际上求的就是多边形内部能看到每条边的区域的交。


我们可以用“凹”字模拟一下这个算法:




把8个点这么编号,这里是按照逆时针编号的。

首先,看12所在直线,显然对于12而言,所有点都在他的内部,于是kernel = 所有点;

之后看23,对23而言,恰好在其直线上和在其下方的点是在他的内部,同样kernel = 所有点;

看34, 就不一样了。显然5678都不在对他而言的多边形的内部,因为能看到34的多边形区域是


之后便按个检查,下方那个点是在检查8点时,发现1点(8号点相邻的点)在内部,于是1-8 和 3-4 交点入kernel。

现在kernel是1234X, X是新来的交点。

之后检查45,你会发现蓝色区域又变小了(懒得画了,变到只剩下右下角了),然后检查56的时候,就没有了。于是我们知道这个图形没有核。


算法复杂度是N平方的,听说有NlgN做法,不过这个N平方的我就想了半天……


贴个代码吧

#include<iostream>
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define D(x) cout<<#x<<" "<<x<<endl
using namespace std;

const double EPS = 1e-8;
int sgn(double x)
{
	if(fabs(x) < EPS)
		return 0;
	if(x>0)
		return 1;
	return -1;
}

struct Point
{
	double x,y;
	Point(){};
	Point(double _x, double _y)
	{
		x=_x;
		y=_y;
	}
	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;
	}
};
Point ori_point[110]; // origin set

Point point[110]; // kernel set
int p_point=0;

void getLine(Point p1, Point p2, double &a, double &b, double &c)
{
	a = p2.y - p1.y;
	b = p1.x - p2.x;
	c = p2.x*p1.y - p1.x*p2.y;
	return;
}

Point Intersection(Point p1,Point p2,double a,double b,double c)
{
	double u = fabs(a*p1.x + b*p1.y + c);
	double v = fabs(a*p2.x + b*p2.y + c);
	Point t;
	t.x = (p1.x*v + p2.x*u)/(u+v);
	t.y = (p1.y*v + p2.y*u)/(u+v);
	return t;
}

Point tp[110];

void cut(double a, double b, double c, Point p[], int &cnt)
{
	int tmp = 0;
	for(int i = 1;i <= cnt;i++)
	{
		//当前点在左侧,逆时针的点
		if(sgn(a*p[i].x + b*p[i].y + c) <= 0)
			tp[++tmp] = p[i];
		else
		{
			if( sgn (a*p[i-1].x + b*p[i-1].y + c ) < 0)
			{
				tp[++tmp] = Intersection(p[i-1],p[i],a,b,c);
			}
			if( sgn (a*p[i+1].x + b*p[i+1].y + c) < 0)
				tp[++tmp] = Intersection(p[i],p[i+1],a,b,c);
		}
	} 
	for(int i = 1;i <= tmp;i++)
	{
		p[i] = tp[i];
	}
	p[0] = p[tmp];
	p[tmp+1] = p[1];
	cnt = tmp;
}

int n;
int t;

void init()
{
	memset(point, 0, sizeof(point));
	return;
}
int main()
{
	scanf("%d", &t);
	int files;
	for(files=1; files<=t; files++)
	{
		init();
		scanf("%d", &n);
		int i;
		for(i=1;i<=n;i++)
			scanf("%lf %lf", &point[i].x, &point[i].y);
		
		for(i=1; i<=n/2;i++)
		{
			swap(point[i], point[n+1-i]);
		}
		
		point[n+1] = point[1];
		point[0] = point[n];
		
		memcpy(ori_point, point, sizeof(point));
		p_point = n;
		
		for(i=1;i<=n; i++)
		{
			double a,b,c;
			getLine(ori_point[i], ori_point[i+1], a,b,c);
			cut(a,b,c, point, p_point);
		}
		
		if(p_point>0)
			printf("YES\n");
		else
			printf("NO\n");
	}
	system("pause");
	return 0;
}

















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值