首页 > 代码库 > 【BZOJ2618】【Cqoi2006】凸多边形 半平面交 、算法的深度细节剖析。

【BZOJ2618】【Cqoi2006】凸多边形 半平面交 、算法的深度细节剖析。

题解:虽然这道题数据范围太小,O(n*n)的算法都能过,但是我为了练手仍写了半平面交。。

半平面交:

我们规定:一个基准点+一个向量(本质是有向直线,)就算一个半平面,现在要求出半平面交,然后做一些事情。。

那么可以过每个半平面的基准点做一条平行于x轴的向右的射线,此射线与向量代表直线的夹角为其“极角”。。


我们可以把所有半平面(Line,或者说叫有向直线)以其极角为键值排序,

然后扫一圈,围出来一个图形,即要求的半平面交。。


实现过程不妨看代码,有详细注释。


代码中有几个需要画图的地方,图将会被附在代码后面~

先发个模板:

struct Point
{
	double x,y;
	Point(double _x,double _y):x(_x),y(_y){}
	Point(){}
	void read(){scanf("%lf%lf",&x,&y);}
	Point operator + (const Point &a)const{return Point(x+a.x,y+a.y);}
	Point operator - (const Point &a)const{return Point(a.x-x,a.y-y);}
	Point operator * (const double a)const{return Point(x*a,y*a);}
}P[N];
struct Line
{
	Point u,v;
	double Ang;
	Line(Point _u,Point _v):u(_u),v(_v){Ang=atan2(v.y,v.x);}
	Line(){}
	bool operator < (const Line &a)const{return Ang<a.Ang;}
}L[N];
double xmul(Point a,Point b){return a.x*b.y-a.y*b.x;}
inline bool onleft(Point a,Line A){return xmul(A.v,A.u-a)>eps;}
Point Line_intersection(Line a,Line b)
{
	Point u=a.u-b.u;
	double t=xmul(u,b.v)/xmul(a.v,b.v);
	return a.u+a.v*t;
}
int half_planes_intersection(int size)
{
	sort(L+1,L+size+1);
	int i,l,r;
	Line q[N];
	q[l=r=1]=L[1];
	Point p[N];
	for(i=2;i<=size;i++)
	{
		while(l<r&&!onleft(p[r-1],L[i]))r--;
		while(l<r&&!onleft(p[l],L[i]))l++;
		q[++r]=L[i];
		if(fabs(xmul(q[r].v,q[r-1].v))<eps)
		{
			r--;
			if(onleft(L[i].u,q[r]))q[r]=L[i];
		}
		if(l<r)p[r-1]=Line_intersection(q[r-1],q[r]);
	}
	while(l<r&&!onleft(p[r-1],q[l]))r--;
	if(r-l<=1)return 0;
	p[r]=Line_intersection(q[l],q[r]);
	int m=0;
	for(i=l;i<=r;i++)P[++m]=p[i];
	return m;
}


再发代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 2000
#define eps 1e-10
using namespace std;
int cnt;
struct Point
{
	double x,y;
	Point(double _x,double _y):x(_x),y(_y){}
	Point(){}
	void read(){scanf("%lf%lf",&x,&y);}
	// 重载向量加、两点之间向量、向量乘
	Point operator + (const Point &a)const{return Point(x+a.x,y+a.y);}
	Point operator - (const Point &a)const{return Point(a.x-x,a.y-y);}
	Point operator * (const double a)const{return Point(x*a,y*a);}
}P[N];// P:记录凸多边形上的点
struct Line
{
	Point u,v;// u表示基准点,v是向量
	double Ang;// 极角
	Line(Point _u,Point _v):u(_u),v(_v){Ang=atan2(v.y,v.x);}
	Line(){}
	// 按极角排序、两直线平行的情况于半平面交函数中特判丶
	bool operator < (const Line &a)const{return Ang<a.Ang;}
}L[N];// 记录所有的边、取线左侧为所需半平面、、
double xmul(Point a,Point b){return a.x*b.y-a.y*b.x;}// 叉积
inline bool onleft(Point a,Line A){return xmul(A.v,A.u-a)>eps;}
//判断点a是否在直线A的左侧、1左0右
Point Line_intersection(Line a,Line b)//两直线交点、
{
	Point u=a.u-b.u;
	double t=xmul(u,b.v)/xmul(a.v,b.v);
	return a.u+a.v*t;//向量长度是原来的t倍、
}
int half_planes_intersection(int size)
{
	sort(L+1,L+size+1);
	// 按照极角排一下序、
	int i,l,r;
	Line q[N];
	q[l=r=1]=L[1];
	Point p[N];
	for(i=2;i<=size;i++)
	{
		//新的半平面切得比较多,
		//甚至废掉了之前的某些半平面,
		//就需要出队一些元素

		//这三个while的具体删除部分见博客鼠绘
		while(l<r&&!onleft(p[r-1],L[i]))r--;
		while(l<r&&!onleft(p[l],L[i]))l++;
		q[++r]=L[i];//因为排过序,所以无论如何先把半平面加进去
		if(fabs(xmul(q[r].v,q[r-1].v))<eps)
		{
			//如果两个直线(半平面)平行,,
			r--;//那就肯定有一个是废的
/**/		if(!onleft(p[r-1],L[i]))q[r]=L[i];
		}
		if(l<r)p[r-1]=Line_intersection(q[r],q[r-1]);
		//求两条直线交点、即算出当前多出来的一个凸多边形上点
	}
	while(l<r&&!onleft(p[r-1],q[l]))r--;
	if(l+1>=r)return 0;//半平面交把平面交没了~~
	p[r]=Line_intersection(q[l],q[r]);
	//最后再求一次交点、、、
	int m=0;
	for(i=l;i<=r;i++)P[++m]=p[i];
	//把在凸多边形上的点copy出来,模板式,常数不太好。
	return m;//返回凸多边形上点数。
}
double asks(int size)//求面积,,原理我会在博客上给鼠绘。
{
	double ret=0;
	for(int i=1;i<=size;i++)ret+=xmul(P[i],P[i==size?1:i+1]);
	return fabs(ret/2.0);
}
Point po[55];//临时记录 点 (非模板内容)
int main()
{
//	freopen("test.in","r",stdin);
	int i,g,n;
	scanf("%d",&g);
	while(g--)
	{//每个凸多边形加点数个边、、
		scanf("%d",&n);
		for(i=1;i<=n;i++)po[i].read();
		L[++cnt]=Line(po[1],po[n]-po[1]);
		for(i=2;i<=n;i++)L[++cnt]=Line(po[i],po[i-1]-po[i]);
	}
	//函数处理。。
	int num=half_planes_intersection(cnt);
	printf("%.3lf",asks(num));
	return 0;
}


学校电脑有点渣,,图片晚上回家后填坑——2014.12.04,如果我没填,请留个言提醒我谢谢。

【BZOJ2618】【Cqoi2006】凸多边形 半平面交 、算法的深度细节剖析。