【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,如果我没填,请留个言提醒我谢谢。

时间: 2024-08-11 19:19:30

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

半平面交算法及简单应用

半平面:一条直线把二维平面分成两个平面. 半平面交:在二维几何平面上,给出若干个半平面,求它们的公共部分 半平面交的结果:1.凸多边形(后面会讲解到)2.无界,因为有可能若干半平面没有形成封闭3.直线,线段,点,空(属于特殊情况吧) 算法:1:根据上图可以知道,运用给出的多边形每相邻两点形成一条直线来切割原有多边形,如果多边形上的点i在有向直线的左边或者在直线上即保存起来,否则判断此点的前一个点i-1和后一个点i+1是否在此直线的左边或线上,在的话分别用点i和点i-1构成的直线与此时正在切割的直

bzoj2618[Cqoi2006]凸多边形 半平面交

这是一道半平面交的裸题,第一次写半平面交,就说一说我对半平面交的理解吧. 所谓半平面交,就是求一大堆二元一次不等式的交集,而每个二元一次不等式的解集都可以看成是在一条直线的上方或下方,联系直线的标准方程就可以得出.于是乎这些不等式就可以转化为一些半平面,求的就是半平面交. 而半平面交不可能交出凹多边形(因为凹多边形的定义是有一条边所在的直线能把该多边形分成若干块...YY一下就知道这是不可能的),这是一个十分优美的性质,正类似于凸包(写法也是有些相似的),但半平面交可能交出无界,于是可以加四条类

BZOJ-2618 [CQOI2006]凸多边形

半平面交模版题.. #include <cstdlib> #include <cstdio> #include <cmath> #include <cstring> #include <cctype> #include <algorithm> #define rep(i, l, r) for(int i=l; i<=r; i++) #define clr(x, c) memset(x, c, sizeof(x)) #define

【半平面交】bzoj2618 [Cqoi2006]凸多边形

#include<cstdio> #include<cmath> #include<algorithm> using namespace std; #define EPS 0.0000001 #define N 511 typedef double db; const db PI=acos(-1.0); struct Point{db x,y;}; typedef Point Vector; Vector operator - (const Point &a,c

BZOJ2618 [Cqoi2006]凸多边形

那个叫啥,半平面交... 第一次写于是只能按照惯例,orz hzwer去~~~ 把一个凸多边形搞成好多条线段,于是题目就变成了一堆线段的半平面交... 怎么感觉比仙人掌还简单一点的说...就是有点长 1 /************************************************************** 2 Problem: 2618 3 User: rausen 4 Language: C++ 5 Result: Accepted 6 Time:0 ms 7 Memo

半平面交模板 HDU 1469

题意就是要判断一个多边形是否存在核. 我们可以把沿着顺时针方向走这个多边形,对于每个边向量,我们取其右边的半平面,判断交是否为空即可. 对于半平面交算法,我只理解了O(n^2)的算法,大概就是用向量去切割多边形,对于O(nlogn)的算法,我从网上各种搜集以及参考了蓝书的实现,给出了一份能看的代码. O(n^2) 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cmat

LA 2218 (半平面交) Triathlon

题意: 有n个选手,铁人三项有连续的三段,对于每段场地选手i分别以vi, ui 和 wi匀速通过. 对于每个选手,问能否通过调整每种赛道的长度使得他成为冠军(不能并列). 分析: 粗一看,这不像一道计算几何的题目. 假设赛道总长度是1,第一段长x,第二段长y,第三段则是1-x-y 那么可以计算出每个选手完成比赛的时间Ti 对于选手i,若要成为冠军则有Ti < Tj (i ≠ j) 于是就有n-1个不等式,每个不等式都代表一个半平面. 在加上x>0, y>0, 1-x-y>0 这三个

【BZOJ 2618】 2618: [Cqoi2006]凸多边形 (半平面交)

2618: [Cqoi2006]凸多边形 Description 逆时针给出n个凸多边形的顶点坐标,求它们交的面积.例如n=2时,两个凸多边形如下图: 则相交部分的面积为5.233. Input 第一行有一个整数n,表示凸多边形的个数,以下依次描述各个多边形.第i个多边形的第一行包含一个整数mi,表示多边形的边数,以下mi行每行两个整数,逆时针给出各个顶点的坐标. Output 输出文件仅包含一个实数,表示相交部分的面积,保留三位小数. Sample Input 2 6 -2 0 -1 -2 1

poj 3384 Feng Shui 半平面交的应用 求最多覆盖凸多边形的面积的两个圆 的圆心坐标

题目来源: http://poj.org/problem?id=3384 分析: 用半平面交将多边形的每条边一起向"内"推进R,得到新的多边形(半平面交),然后求多边形的最远两点. 代码如下: const double EPS = 1e-10; const int Max_N = 105 ; struct Point{ double x,y; Point(){} Point(double x, double y):x(x),y(y){} Point operator - (Point