题解 P2742 【【模板】二维凸包 / [USACO5.1]圈奶牛Fencing the Cows】
更不好的阅读体验
凸包
一道模板题
首先凸包的定义:
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
(百度百科给的解释)
因此以下就可以算是一个凸包
求法有两个
求法 1
第一个是把凸包分为上下两部分,对于下半部分不难发现斜率是在递增的,因此考虑先给所有点按照从左到右从下到上排序,然后类似于单调栈维护一个斜率。
然后伪代码大概就长这样
for(i=1;i<=n;i++)
{
s[++size]=p[i];
while(size>=3&&getk(s[size-2],s[size])<getk(s[size-2],s[size-1])) s[size-1]=s[size],size--;
}
例如我们以左下角E(优先考虑最下)点对这些点极角排序,显然可见a
ABCDEFG的坐标:
7
-1.56 -1.11
1.68 -1.63
2.12 2.29
-2 1
-2.66 -3.87
2.46 -3.39
-4.7 -1.73
排序的结果是这样的(FBCADG)可以理解为从0度逆时针扫一圈。
atan2 (x,y)当y=0时,
2.叉积
如图所示
公式
原理可以参考下面这张出,出处大概在这里,由于图片加载太慢我放在图床上面了
同时四边形面积大于0说明p2在p1顺时针方向,如果==0重合,如果小于0就说明在逆时针方向
算叉积的时候可以用这个公式
c如果在a右边的话,
同理,如果上面的式子大于0,就说明c不在凸包里面。
如下图所示
刚开始d,b在栈里面,此时比较(d,c,b),发现>0则c入栈
同理对于d,比较(b,d,c)也大于0,入栈
运行完毕,发现
对于小于O的情况的解释:
例如 b,c,N三个点,按照极角排序,就是b,c,N,现在我们假设b,c在栈里面,就要比较(b,N,c),此时小于0,即c并不在凸包上(N取代了他的位置)
实际上推完了上面的东西,你就能写出来代码了.
#include<touwenjian.h>
using namespace std;
const double eps=1e-3;
int n,m,size;
double ans;
struct node{
double x,y;
}p[200200],s[200200];
inline int cmp(node a,node b)
{
double k1,k2;
k1=atan2((a.y-p[1].y),(a.x-p[1].x));
k2=atan2((b.y-p[1].y),(b.x-p[1].x));
if(k1!=k2) return k1<k2;
return a.x<b.x;
}
inline double getdis(node a,node b)
{
return sqrt(1.0*(a.x-b.x)*(a.x-b.x)+1.0*(a.y-b.y)*(a.y-b.y)); //上面讲的很清楚了
}
inline double getcj(node a,node b,node c)
{
return 1ll*(b.x-a.x)*(c.y-a.y)-1ll*(b.y-a.y)*(c.x-a.x);
}
inline int bj(node a,node b)
{
return (a.y>b.y||(a.y==b.y)&&(a.x>b.x));
}
void gettb()
{
p[0].x=p[0].y=0x3f3f3f3f;
register int i,j;
for(i=1;i<=n;i++)
{
if(bj(p[0],p[i])) p[0]=p[i],j=i; //找到最小边的点,并且把他放到第一位
}
swap(p[j],p[1]);
sort(p+2,p+n+1,cmp); //对后面的点极点排序
s[++size]=p[1];s[++size]=p[2]; //让前两个点入栈(他们一定是在凸包上)
for(i=3;i<=n;)
{
if(size>=2&&getcj(s[size-1],p[i],s[size])>=0) size--; //就是上面说的
else s[++size]=p[i++];
}
s[0]=s[size]; //记得凸包要首尾相连
for(i=1;i<=size;i++) ans+=getdis(s[i-1],s[i]); //算距离即可
}
int main()
{
register int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++) scanf("%lf %lf",&p[i].x,&p[i].y);
gettb();
printf("%.2lf",ans);
return 0;
}