P2742 [USACO5.1]圈奶牛Fencing the Cows
题目描述
农夫约翰想要建造一个围栏用来围住他的奶牛,可是他资金匮乏。他建造的围栏必须包括他的奶牛喜欢吃草的所有地点。对于给出的这些地点的坐标,计算最短的能够围住这些点的围栏的长度。
输入输出格式
输入格式:
输入数据的第一行包括一个整数 N。N(0 <= N <= 10,000)表示农夫约翰想要围住的放牧点的数目。接下来 N 行,每行由两个实数组成,Xi 和 Yi,对应平面上的放牧点坐标(-1,000,000 <= Xi,Yi <= 1,000,000)。数字用小数表示。
输出格式:
输出必须包括一个实数,表示必须的围栏的长度。答案保留两位小数。
输入输出样例
输入样例#1:
4
4 8
4 12
5 9.3
7 8
输出样例#1:
View Code
12.00
题解:凸包。该题是一题裸的凸包问题,只需要将凸包轮廓上的点组成封闭多边形的周长计算出来即可。
下面介绍一下凸包的算法。
我们不难发现,如果将与凸包边上的一个点相邻的两条边中的一条调整到连接凸包内部的一个点,然后再从这个内部的点连回外面轮廓上的该点,我们发现凸包的形状就会发生改变,其弯曲的幅度(即夹角)会变陡,而且出现了凹进去的形状。
针对这点,我们可以想到一个排序方法,首先将纵坐标最小的点放到点集的第一个位置,因为它一定是凸包轮廓上的一点。
紧接着,我们根据第一个点到两个点p,q的向量叉积排序,叉积大于零,就把p放在前面;叉积等于零就将距离第一个点近的点放在前面,这里只需要处理一个cmp,然后sort就行。
我们将排好序的点集求凸包。
设原点集为a,首先将第一个点放入轮廓点集p中,然后从第2个点向第n个点找。
设我们轮廓点集已经有cnt个点,如果找到点a[i],使得已经放好的点集中向量p[cnt-1]-p[cnt]与向量p[cnt]-a[i]的叉积小于等于零,说明a[i]可以直接与p[cnt-1]相连更优,于是我们将p[cnt]弹出,循环到叉积大于零为止,再把a[i]放进轮廓点集。
根据这样的算法,我们就可以将凸包轮廓点集p求出,再按顺序算相邻两点间的长度即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=10005; 4 struct node{ 5 double x,y; 6 }tmp,a[N],p[N]; 7 int n,cnt=1; 8 double c; 9 node Minus(node a,node b) 10 { 11 node res; 12 res.x=a.x-b.x; res.y=a.y-b.y; 13 return res; 14 } 15 double dis(node a,node b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));} 16 double cross(node a,node b){return a.x*b.y-b.x*a.y;} 17 bool cmp(node p,node q){ 18 double tmp=cross(Minus(a[1],p),Minus(a[1],q)); 19 if (tmp>0) return 1; 20 if (tmp==0&&dis(a[1],p)<dis(a[1],q)) return 1; 21 return 0; 22 } 23 int main() 24 { 25 scanf("%d",&n); 26 for (int i=1;i<=n;++i) 27 scanf("%lf%lf",&a[i].x,&a[i].y); 28 for (int i=2;i<=n;++i) 29 if (a[i].y<a[1].y) tmp=a[i],a[i]=a[1],a[1]=tmp; 30 sort(a+2,a+n+1,cmp); 31 p[1]=a[1]; 32 for (int i=2;i<=n;++i) 33 { 34 while (cnt>1&&cross(Minus(p[cnt-1],p[cnt]),Minus(p[cnt],a[i]))<=0) --cnt; 35 p[++cnt]=a[i]; 36 } 37 for (int i=1;i<=cnt;++i) 38 c+=dis(p[i],p[i%cnt+1]); 39 printf("%0.2lf ",c); 40 return 0; 41 }