HDU 4667 Building Fence(求凸包的周長)

A - Building Fence
Time Limit:1000MS     Memory Limit:65535KB     64bit IO Format:%I64d & %I64u
Submit Status

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.
 

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.
 

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了,正確做法如下:
   把三角形的所有點視爲單個點,放到一個集合P中,求這些點與圓的切線的交點,
   把這些點再放到集合P中。再求圓與圓之間的內切線外切線與圓的交點,再放到
   集合P中,把這些點進行凸包就可以求出周長了。
   有些細節問題需要注意,已寫到註釋裏。
   另外注意在沒有三角形只有一個圓的情況下要單獨考慮,因爲不會通過點和圓之間
   的切線以及圓和圓之間的切線產生點,所以要單獨判斷然後輸出圓的面積,
   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));
}
//叉積
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)//點到圓的切線的切點,考慮不同情況
{
    Point u=p-o.c;
    double d=Length(u);
    double a=atan2(u.y,u.x);
    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是極角
    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;
        }
        if(n==1&&!m)
        {
            printf("%.5f\n",PI*2*c[0].r);; //輸出一個圓的面積
            continue;
        }
            // 注意 swap是把數值換位置 n和m不換位置
        for(int i=m-1; i>=0; i--)
            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-1].id=j;//給新的點做標記 這個點是第j個圓的
                }
            }
        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-1].id=j;
                    p[m++]=b[k];
                    p[m-1].id=i;
                }
            }
        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;
}

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章