HDU 4667 Building Fence(求凸包的周长)
Description
Long long ago, there is a famous farmer named John. He owns a big farm and many cows. There are two kinds of cows on his farm, one is Friesian, and another one is Ayrshire. Each cow has its own territory. In detail, the territory of Friesian is a circle, and of Ayrshire is a triangle. It is obvious that each cow doesn't want their territory violated by others, so the territories won't intersect.
Since the winter is falling, FJ has to build a fence to protect all his cows from hungry wolves, making the territory of cows in the fence. Due to the financial crisis, FJ is currently lack of money, he wants the total length of the fence minimized. So he comes to you, the greatest programmer ever for help. Please note that the part of fence don't have to be a straight line, it can be a curve if necessary.
Since the winter is falling, FJ has to build a fence to protect all his cows from hungry wolves, making the territory of cows in the fence. Due to the financial crisis, FJ is currently lack of money, he wants the total length of the fence minimized. So he comes to you, the greatest programmer ever for help. Please note that the part of fence don't have to be a straight line, it can be a curve if necessary.
Input
The input contains several test cases, terminated by EOF. The number of test cases does not exceed 20.
Each test case begins with two integers N and M(0 ≤ N, M ≤ 50, N + M > 0)which denotes the number of the Friesian and Ayrshire respectively. Then follows N + M lines, each line representing the territory of the cow. Each of the first N lines contains three integers Xi, Y i, R i(1 ≤ R i ≤ 500),denotes the coordinates of the circle's centre and radius. Then each of the remaining M lines contains six integers X1 i, Y1 i, X2 i, Y2 i, X3 i, Y3 i, denotes the coordinates of the triangle vertices. The absolute value of the coordinates won't exceed 10000.
Each test case begins with two integers N and M(0 ≤ N, M ≤ 50, N + M > 0)which denotes the number of the Friesian and Ayrshire respectively. Then follows N + M lines, each line representing the territory of the cow. Each of the first N lines contains three integers Xi, Y i, R i(1 ≤ R i ≤ 500),denotes the coordinates of the circle's centre and radius. Then each of the remaining M lines contains six integers X1 i, Y1 i, X2 i, Y2 i, X3 i, Y3 i, denotes the coordinates of the triangle vertices. The absolute value of the coordinates won't exceed 10000.
Output
For each test case, print a single line containing the minimal fence length. Your output should have an absolute error of at most 1e-3.
Sample Input
1 1
4 4 1
0 0 0 2 2 0
Sample Output
15.66692
Hint
Please see the sample picture for more details, the fence is highlighted with red.
题意:给定n个圆,m个三角形,求凸包的周长,详见上图。
题解:这个题有一种很水的做法就是把圆分成1000个点,然后直接对这些点求凸包。
不推荐这个方法,属于水过的,换个精度高的数据没准就WA了,正确做法如下:
把三角形的所有点视为单个点,标记三角形每个点的id为-1,-2,-3,-4.......放到一个集合P中,
求这些点与圆的切线的交点,把这些点再放到集合P中,标记他们的id为圆的数组下标。
再求圆与圆之间的内切线外切线与圆的交点,再放到集合P中,标记他们的id为圆的数组下标,
把这些点进行凸包就可以求出周长了,id相同的点要算弧长。很多很多细节问题需要注意,已写到注释里。
另外注意在没有三角形只有一个圆的情况下要单独考虑,因为不会通过点和圆之间
的切线以及圆和圆之间的切线产生点,所以要单独判断然后输出圆的面积,
continue即可。
对于数组为什么开2W,目前未知,求大神解答。
#include <iostream> #include <math.h> #include <stdio.h> #include <string.h> #include <algorithm> #include <stdlib.h> #include <vector> const double PI=acos(-1.0); using namespace std; struct Point{ double x,y; int id; Point(double x=0,double y=0,int id=-1):x(x),y(y){} //构造函数,方便代码编写 }; typedef Point Vector; //从程序上实现,Vector只是Point的别名 struct Circle{ Point c; double r; Circle() {} Circle(Point c,double r):c(c),r(r){} Point point(double a){ return Point(c.x+cos(a)*r,c.y+sin(a)*r); } }; //定义 #define N 50000 //数组最小开20000 为什么开20000没算对 //我算的是 150+300+C(50,2)*4 即三角形的点+这些点与圆的切点+圆之间的切点 不到6000 //开大点就完了 开500000 才 42344KB 上限65536 够用了 Point p[N]; Point ch[N]; Circle c[N]; Point a[10],b[10]; Point q[N]; int m,n,t; //点-点=向量 Vector operator - (Point A,Point B) { return Vector(A.x-B.x,A.y-B.y); } //运算符重载 bool operator <(const Point &a,const Point &b) { return a.x<b.x||(a.x==b.x&&a.y<b.y); } const double eps=1e-10; //三态函数精度问题 int dcmp(double x) { if(fabs(x)<eps) return 0; else return x<0?-1:1; } bool operator ==(const Point &a,const Point &b) { return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0; } double Dot(Vector A,Vector B) { return A.x*B.x+A.y*B.y; } //求向量的值 double Length(Vector A) { return sqrt(Dot(A,A)); } //求夹角 double Angle(Vector A,Vector B) { return acos(Dot(A,B)/Length(A)/Length(B)); //a*b=|a|*|b|*cos(c) } //叉积 double Cross(Vector A,Vector B) { return A.x*B.y-A.y*B.x; } //求凸包模板 int ConvexHull(Point *p,int n,Point* ch) //注意是*ch { sort(p,p+n); //这个要用到<重载运算符 n=unique(p,p+n)-p; //这个要用到==重载运算符 int m=0; for(int i=0;i<n;i++) { //注意:可以共线时"<"改为"<=" while(m>1&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--; ch[m++]=p[i]; } int k=m; for(int i=n-2;i>=0;i--) //注意是--不是++ { while(m>k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0) m--; ch[m++]=p[i]; } if(n>1) m--; return m;//别忘了加m } Point readpoint() { double x,y; scanf("%lf%lf",&x,&y); return Point(x,y); } void readcircle(Circle &c) { scanf("%lf%lf%lf",&c.c.x,&c.c.y,&c.r); } //点到圆的切线 v存的是切点 int pcl(Point p,Circle o,Vector* v)//点到圆的切线的切点,考虑不同情况 { Vector u=p-o.c; double d=Length(u); double a=atan2(u.y,u.x);//指向量u与x正半轴的夹角 起点是圆心 指向p点 if(d<o.r)return 0;//圆内 else if(dcmp(d-o.r)==0)//圆上 { v[0]=o.point(a); return 1; } else //圆外 两个外切点 { double ang=acos(o.r/d); v[0]=o.point(a+ang); v[1]=o.point(a-ang); return 2; } } //两圆相离的时候的外公共切线 int ccl(Circle c1,Circle c2,Point *a,Point *b) { int cnt=0; if(dcmp(c1.r-c2.r)<0)//保证结果为正值 { swap(c1,c2); swap(a,b); } Vector u=c2.c-c1.c; double d=Length(u); double ang=atan2(u.y,u.x); //有外公切线 double g=acos((c1.r-c2.r)/d); a[cnt]=c1.point(ang+g); b[cnt]=c2.point(ang+g); cnt++; a[cnt]=c1.point(ang-g); b[cnt]=c2.point(ang-g); cnt++; return cnt; } double arc(Point a,Point b,Circle c)//a,b逆时针穿过圆c外面的的圆弧长 { Point u=a-c.c,v=b-c.c; double ang=Angle(u,v); //Angle 是夹角 模板中的angle=atan2(v.y,v.x)是极角 if(Cross(u,v)>eps) return ang*c.r; return (PI*2.0-ang)*c.r; } int main() { while(scanf("%d%d",&n,&m)==2) { m=m*3; for(int i=0; i<n; i++) //圆 readcircle(c[i]); for(int i=0; i<m; i++) //三角形 { p[i]=readpoint(); p[i].id=-i*3-1; //三角形的id标记成负数 } if(n==1&&!m) { printf("%.5f\n",PI*2*c[0].r);; //输出一个圆的面积 continue; } // 注意 swap是把数值换位置 n和m不换位置 for(int i=m-1; i>=0; i--) //三角形的点 已经乘过3了 for(int j=0; j<n; j++) //圆 { Point v[2]; //存放切点 int num=pcl(p[i],c[j],v); //点到圆的切线 p是点 c是圆 v是切点 num是切点个数 for(int k=0; k<num; k++) { //从三角形的点中继续添加点 p[m]=v[k];//把切点放入点集中 p[m].id=j;//给新的点做标记 这个点是第j个圆的 m++; } } for(int j=0; j<n; j++) for(int i=j+1; i<n; i++) { int num=ccl(c[j],c[i],a,b); //圆和圆之间的外切点 for(int k=0; k<num; k++) { p[m]=a[k]; p[m].id=j; m++; p[m]=b[k]; p[m].id=i; m++; } } m=ConvexHull(p,m,ch); memcpy(p,ch,sizeof(ch)); double len=0.0; p[m]=p[0]; for(int i=0; i<m; i++) { if(p[i].id==p[i+1].id) //如果标记相等说明在一个圆上 { len+=arc(p[i],p[i+1],c[p[i].id]); //两个点绕圆弧逆时针的长 } else { len+=Length(p[i]-p[i+1]);//不在一个圆上输出算直线距离 } } printf("%.5f\n",len); } return 0; }