圈奶牛 Fencing the Cows
链接
Luogu_P2742 圈奶牛 Fencing the Cows /【模板】二维凸包
题目描述
农夫约翰想要建造一个围栏用来围住他的奶牛,可是他资金匮乏。他建造的围栏必须包括他的奶牛喜欢吃草的所有地点。对于给出的这些地点的坐标,计算最短的能够围住这些点的围栏的长度。
输入格式
输入数据的第一行是一个整数。表示农夫约翰想要围住的放牧点的数目 nn。
第 2 2 2到第 ( n + 1 ) (n+1) (n+1)行,每行两个实数,第 ( i + 1 ) (i+1) (i+1)行的实数 x i , y i x_i, y_i xi,yi分别代表第 i i i个放牧点的横纵坐标。
输出格式
输出输出一行一个四舍五入保留两位小数的实数,代表围栏的长度。
输入输出样例
输入 #1
4
4 8
4 12
5 9.3
7 8
输出 #1
12.00
说明/提示
数据规模与约定
对于 100 % 100\% 100%的数据,保证 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105, − 1 0 6 ≤ x i , y i ≤ 1 0 6 -10^6 \leq x_i, y_i \leq 10^6 −106≤xi,yi≤106。小数点后最多有 2 2 2位数字。
思路
这题是听完课之后做的模板题。
凸包(Convex Hull)是一个计算几何(图形学)中的概念。 在一个实数向量空间 V V V中,对于给定集合 X X X,所有包含 X X X的凸集的交集 S S S被称为 X X X的凸包。 X X X的凸包可以用 X X X内所有点( X 1 , . . . , X n X_1,...,X_n X1,...,Xn)的凸组合来构造。 在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它能包含点集中所有点的。(引用自百度百科)
本题要我们求的就是农场上所有放牧点的凸包的长度。
解二维凸包问题常用的方法是:
- 找到一个 y y y坐标最小( y y y坐标相同就找 x x x坐标最小)的点,作为凸包的起点;
- 把剩余的每个点分别与起点联结,并判断这条线段所在的直线与 x x x轴的夹角的大小,按照这个夹角的大小把每个点从小到大排序;
- 把每个点依次加入栈,对于每个即将入栈的点 P i P_i Pi,判断线段 P i P t o p P_iP_{top} PiPtop是否在线段 P t o p P t o p − 1 P_{top}P_{top-1} PtopPtop−1的逆时针方向,如果是,则将点 P i P_i Pi加入栈,否则,弹出 P t o p P_{top} Ptop,如此循环,直至 P i P_i Pi入栈;
- 最后,栈中的每个点连起来就是这些点的凸包;
- 这个算法叫做Garham算法;
判断线段 A B AB AB是否在线段 C D CD CD的逆时针方向,我们要用到叉积。
设点
A
,
B
,
C
,
D
A,B,C,D
A,B,C,D的
x
,
y
x,y
x,y坐标分别为
A
.
x
,
A
.
y
,
B
.
x
,
B
.
y
,
C
.
x
,
C
.
y
,
D
.
x
,
D
.
y
A.x,A.y,B.x,B.y,C.x,C.y,D.x,D.y
A.x,A.y,B.x,B.y,C.x,C.y,D.x,D.y,那么线段
A
B
AB
AB与
C
D
CD
CD所在向量的叉积的计算公式就是:
P
(
A
,
B
,
C
,
D
)
=
(
B
.
x
−
A
.
x
)
∗
(
D
.
y
−
C
.
y
)
−
(
D
.
x
−
C
.
x
)
∗
(
A
.
y
−
B
.
y
)
P(A,B,C,D)=(B.x-A.x)*(D.y-C.y)-(D.x-C.x)*(A.y-B.y)
P(A,B,C,D)=(B.x−A.x)∗(D.y−C.y)−(D.x−C.x)∗(A.y−B.y)
如果
P
(
A
,
B
,
C
,
D
)
=
0
P(A,B,C,D)=0
P(A,B,C,D)=0,那么
A
B
AB
AB就与
C
D
CD
CD平行;
如果
P
(
A
,
B
,
C
,
D
)
>
0
P(A,B,C,D)>0
P(A,B,C,D)>0,那么
A
B
AB
AB就在
C
D
CD
CD的逆时针方向;
如果
P
(
A
,
B
,
C
,
D
)
>
0
P(A,B,C,D)>0
P(A,B,C,D)>0,那么
A
B
AB
AB就在
C
D
CD
CD的顺时针方向;
这样,我们就可以完成算法中两条线段、直线之间的关系的判断了。
代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<stack>
using namespace std;
typedef long double ld;
typedef long long ll;
const ld inf=100000000.0;
struct poi{
ld x,y;
}p[100001],a[100001];
ll n,cnt,beg;
ld ans;
stack<poi> s;
ll cro_pro(poi a,poi b,poi c,poi d)//求向量叉积(cross product)
{
return (b.x-a.x)*(d.y-c.y)-(d.x-c.x)*(b.y-a.y);
}
ld len(poi a,poi b)//求AB边长
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
bool cmp(poi a,poi b)//按照与x轴夹角的大小排序
{
if(cro_pro(p[1],a,p[1],b)>0)
return true;
else if(cro_pro(p[1],a,p[1],b)==0&&len(p[1],a)<len(p[1],b))
return true;
else
return false;
}
void garham()
{
poi a,b,c;
s.push(p[1]);
s.push(p[2]);
for(ll i=3;i<=n;i++)
{
c=p[i];
b=s.top();
s.pop();
a=s.top();//注意要去除栈顶的前两个元素
while(cro_pro(a,b,b,c)<0)
{
b=s.top();
s.pop();
a=s.top();
}
s.push(b);
s.push(c);
}
}
int main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
{
scanf("%Lf%Lf",&p[i].x,&p[i].y);
if(p[i].y<p[1].y)
swap(p[i],p[1]);
else if(p[i].y==p[1].y&&p[i].x<p[1].x)
swap(p[i],p[1]);
}
sort(p+1+1,p+n+1,cmp);
garham();
while(!s.empty())
{
cnt++;
a[cnt]=s.top();
s.pop();
}
for(ll i=1;i<cnt;i++)
ans+=len(a[i],a[i+1]);
ans+=len(a[cnt],a[1]);
printf("%.2Lf",ans);
return 0;
}