[专辑]计算几何初步

貌似是第一次做计算几何的题, 以前都是了解里面些概念但没做过题....主要是没精力来完善到这一块....而且比赛的时候"疲于奔命"似的做其他被人刷爆的题目.....

边讲题边讲我对计算几何初步的理解.....

POJ 2365 Rope

求一个凸包的周长, 题目已经是按顺序给出凸包的各个点, 所以直接求邻点间距离就为切线距离.
而弧长距离, 由于是内角和是2PI, 所以最后加上一个圆周长即可.
涉及概念:
两点欧式距离
double dist(POINT p1,POINT p2) // 返回两点之间欧氏距离
{
    return( sqrt( (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y) ) );
}

POJ 2007 Scrambled Polygon

给你一个凸包, 其中必有一个原点, 要你将凸包上的点按关于原点(0, 0) 逆时针排序.
思路 :
首先可以得出几个结论, 1/ 所有点最多分布在三个象限. 2/ 原点到任何两个点的向量的夹角必然小于pi.
这题其实是按极角排序, 而极角排序可以依据 叉积大小 或  atan2大小.
涉及概念:
一 极角
两个向量a,b,以a为极轴建极坐标系,b与a的夹角就是极角,极角可以取任意弧度.(据说数学上一般取[0,2pi) ).
二 叉积
两向量a,b的叉积的大小是以a,b为边的平行四边形的面积,符号为逆时针旋转为正,顺时针为负.大量用于判断几何位置关系,也可以用来求面积.
公式为 x1*y2-x2*y1.
三 atan2函数
这个在<cmath>头文件里, 它接受一个向量,返回一个弧度表示这个向量在坐标系上的位置,从-x轴沿原点转一圈回到-x,弧度范围是(-pi,pi].

而这个题是极角排序, 但是我们只能两两比较(偏序关系), 来排序, 我们知道任意两个向量夹角是小于pi的, 那我们正好用叉积可以比, 叉积为正表示逆时针, 为负表示顺时针. 
int cross(point p0,point p1,point p2)
{
    return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x);
}        
bool cmp(point p1,point p2)
{
    int tmp=cross(a[0],p1,p2);
    if(tmp>0) return true;
    else return false;
}  

那用atan2函数, 怎么比呢? 题目没有说所占的三个象限不跨越-x轴, 那 atan2 的差就有可能大于 pi, 或小于 -pi, 也就是说它取的不一定是两个向量小于pi的那个角. 所以我们要经过一些处理 .
bool cmp(const point &a, const point &b)
{
	double angle1 = atan2(a.y, a.x);
	double angle2 = atan2(b.y, b.x);
	double angle = angle1-angle2;
	if(angle>pi) angle-=2*pi;
	if(angle<-pi) angle = 2*pi+angle;
	return angle<0;
}

POJ 1269  Intersecting Lines

题目就叫相交直线, 都直白了吧. 这题就是判下两线段位置关系.
注意: 线段是segment, 直线是line...

涉及概念:
一 直线位置关系
平行: 不相交, 或者覆盖. 可以判断叉积为0就是平行, 覆盖的话可以判断共线
相交: 两直线交与一点.
//计算cross product (P1-P0)x(P2-P0)
double xmult(point p1,point p2,point p0){
return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
//判三点共线
int dots_inline(point p1,point p2,point p3){
return zero(xmult(p1,p2,p3));
}
//判两直线平行
int parallel(line u,line v){
return zero((u.a.x-u.b.x)*(v.a.y-v.b.y)-(v.a.x-v.b.x)*(u.a.y-u.b.y));
}

POJ 1654  Area  

这题就是给一个从原点开始然后又回到原点的路径序列, 求走出的多边形的面积.
我们只要维护前一个点, 然后每次把当前点到前一个点到原点的三角形面积加到结果中就行了.
这里求面积可用用叉积, 求出来的是平行四边形的面积, 最后再除以二即可.
int64 getarea(Point p1,Point p2)
{
 return (int64)p1.x*p2.y-(int64)p2.x*p1.y;		//叉积	为三角形面积两倍
}

POJ 2318  TOYS  

这题只需解决点跟线段的位置关系(点在线段的哪一边)就行了. 可用利用叉积.
//判两点在线段同侧,点在线段上返回0
int same_side(point p1,point p2,line l){
return xmult(l.a,p1,l.b)*xmult(l.a,p2,l.b)>eps;
}
//判两点在线段异侧,点在线段上返回0
int opposite_side(point p1,point p2,line l){
return xmult(l.a,p1,l.b)*xmult(l.a,p2,l.b)<-eps;		//叉积异号
}
那么, 这题要判定的是 一点 在两线段中间(含点在线段上)的情况.只需做一点改变,
//判断两直线在点两边
int is(line l1, line l2, point p)
{
	int ret = xmult(l1.a, p, l1.b)*xmult(l2.a, p, l2.b)>eps;
	return !ret;
}

POJ 3304  Segments  

这题比前面的题都难想点.
显然, 问题等价于是否存在任意一条直线使它过题目中所有线段.
那么, 极限思想, 假设存在这条直线, 我们将它旋转平移, 使它恰好过两个线段的某个端点, 这一定是可以做到的.
那么, 我们只需两两枚举所有线段端点即可, 看过它们的直线是否满足题目.

核心代码:
bool solve()
{
	REP(n<<1)		//枚举点
	{
		FOR(j, i+1, n*2-1)
		{
			if(zero(p[i].x-p[j].x) && zero(p[i].y-p[j].y))
				continue;
			int find = 1;
			FOR(k, 0, n-1)	//枚举线段
			{
				if(!intersection_seg(l[k].a, l[k].b, p[i], p[j]))
				{
					find = 0;
					break;
				}
			}
			if(find) return true;
		}
	}
	return false;
}




写这东西拖了两天,不爽....各种事情..各种欠账...



阅读更多

没有更多推荐了,返回首页