您的位置:首页 > 其它

POJ1113 计算几何雏形:凸包!

2017-03-26 22:12 323 查看


今天pty大神给我们讲了计算几何的基本,能几乎听懂的感觉还是不错的,但不能调出来一道模板题的话,总不能感觉掌握了。嗯,就找了这道堪称计算几何的模板来做了一下,发现,嗯,是我小看了。。。一上午和一下午啊。。的确是有写点东西的必要了。

老样子首先说明一下题意,那个,给你一座城堡,让你在城堡周围修一堵围墙,距离城堡的距离为k,嗯。乍一看有点古怪,其实好好看一下图就知道了,就是城堡的周长加上了一个圆的周长,因为围着城堡绕一圈正好赚了360度对吧,那不就正好一个圆吗,,那就只用考虑城堡的周长就行了嗯。

城堡是通过拐角的点给出的,换言之就是要通过给出的点求出城堡的周长,就是说包围着这些点的“凸包”,求凸包咯!

具体来说怎么做呐,就是GRAHAM求法,即排序+查找;

首先是排序,把所有的点排一遍序,点排序的依据是极角(马上解释),首先找到所有点中左下地方的那个点,就是纵坐标最低,如果纵坐标一样就比较横坐标,找较小的那一个,可以证明这样的一个点一定在凸包的“壳”上,那么就把这么个点记为0号点,从这个点开始找凸壳吧!,找点的代码:

point p0;
scanf("%d%d",&a[0].x,&a[0].y);
p0.x=a[0].x,p0.y=a[0].y;
for(int i=1;i<n;i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
if((p0.y>a[i].y)||(p0.y==a[i].y)&&(p0.x>a[i].x))
{
p0.x=a[i].x,p0.y=a[i].y;
pos=i;
}
}
a[pos]=a[0];
a[0]=p0;


这里的p0只是个临时变量,作用是找到最“矮”的点并把它存到a0里,嗯,就是这样。

然后就是我们的排序了,用快排方便加快捷,但cmp函数要自己打:

bool cmp(const point &aa,const point &b)
{
int ans=cj(a[0],aa,b);
if(ans>0) return true;
else if(ans==0&&dis(a[0],aa)<dis(a[0],b)) return true;
else return false;
}


来解释一下,这里呢,是极角排序,什么是极角呐,就是以我们选定的a0为源点,建立平面直角坐标系,每一个点与源点的连线,与y轴的夹角大小排序,夹角越大,排序越小,这样就能保证依次遍历图时,能够从右边绕到左边来,嗯。但是呐,a0的值最好不要参与排序,因为其他都是按极角排序的,a0是按位置确定的,这样能避免a0的重复计算,嗯。

然后就是关键的一步了,找凸壳!要分一下类:

如果只有一个点的话,凸壳也无所谓了

有2个点的话,凸壳就只有一条边而已,入栈就行了。

有2个以上的边的话,先依次入栈,如果向左转(叉积>0)的话,直接入栈就行了,否则(叉积<0)向右转时就不是凸包了,就把栈弹出,弹到栈首点能够向左转到达时,就把枚举的点入栈!代码:

if(n>2)
{
stack[0]=a[0],stack[1]=a[1];
for(int i=2;i<n;i++)
{
while(top>0&&cj(stack[top-1],stack[top],a[i])<=0)   top--;
stack[++top]=a[i];
}
}


如此,待全部的点都遍历完了后,保存在栈里的点就是凸壳了,把他们的距离依次加起来即可,代码:

for(int i=0;i<=top;i++)
sum+=dis(stack[i],stack[(i+1)%(top+1)]);


呐,完整代码:

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
const double PI=acos(-1.0);
struct point
{
int x,y;
}a[100010],stack[100010];
double sum=0;
int n,k;
double dis(point a,point b)
{
return sqrt((double)(a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int cj(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(const point &aa,const point &b) { int ans=cj(a[0],aa,b); if(ans>0) return true; else if(ans==0&&dis(a[0],aa)<dis(a[0],b)) return true; else return false; }
int main()
{
int pos=0;
scanf("%d%d",&n,&k);
point p0;
scanf("%d%d",&a[0].x,&a[0].y);
p0.x=a[0].x,p0.y=a[0].y;
for(int i=1;i<n;i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
if((p0.y>a[i].y)||(p0.y==a[i].y)&&(p0.x>a[i].x))
{
p0.x=a[i].x,p0.y=a[i].y;
pos=i;
}
}
a[pos]=a[0];
a[0]=p0;
int top=1;
sort(a+1,a+n,cmp);
if(n == 1)
{
top=0;stack[0]=a[0];
}
if(n == 2)
{
top=1;
stack[0]=a[0];
stack[1]=a[1];
}
if(n>2) { stack[0]=a[0],stack[1]=a[1]; for(int i=2;i<n;i++) { while(top>0&&cj(stack[top-1],stack[top],a[i])<=0) top--; stack[++top]=a[i]; } }
for(int i=0;i<=top;i++)
sum+=dis(stack[i],stack[(i+1)%(top+1)]);
printf("%d",(int)(sum+2*PI*k+0.5));
}


如何,不长吧?关于叉积判断方向,就请自己了解下吧,我的算叉积的函数也是直接套用的叉积的公式,证明和推论请参考百度百科!嗯嗯。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: