BZOJ 1043: [HAOI2008]下落的圓盤 計算幾何,貪心,線段交

just_sort發表於2017-01-20

Description

  有n個圓盤從天而降,後面落下的可以蓋住前面的。求最後形成的封閉區域的周長。看下面這副圖, 所有的紅
色線條的總長度即為所求.
Input

  第一行為1個整數n,N<=1000
接下來n行每行3個實數,ri,xi,yi,表示下落時第i個圓盤的半徑和圓心座標.

Output

  最後的周長,保留三位小數
Sample Input
2

1 0 0

1 1 0
Sample Output
10.472

解題方法:看到題目成mengbier了。這個不是圓的周長交吧。。確實不是。。可是不會啊。。看了題解,感覺這個題其實很傻叉。。對於每一個圓i,求出這個圓盤會被後面的每一個圓盤覆蓋哪一段。對於一個圓,可以用-pi到pi的弧度表示區間,然後就可以用線段覆蓋的方法求出區間被覆蓋了多少了。 因此關鍵是對於兩個圓盤i,j,如何求出i被覆蓋的區間。首先對於兩個圓的圓心x,y,可以求出向量(y-x)的atan2值,相當於與x軸正方向的夾角α,範圍-pi到pi。然後根據餘弦定理cos∠A=(b^2+c^2-a^2)/2bc,得到x的圓心到兩圓的一個焦點的射線關於兩圓連心線旋轉角的餘弦,然後用acos得到旋轉角β,那麼α±β就是對應的區間。注意邊界問題,如果下界<-pi,就變成下界+2pi->pi,上界>pi同理。還有要判斷圓相離的情況。。網上的題解看到這個題沒卡精度。所以就沒設eps了。。*一定要好好學習計幾了(騙你的,我才不會)*

程式碼如下:

#include <bits/stdc++.h>
using namespace std;
const double pi = acos(-1.0);
const int N = 2005;
int n, cnt;
struct Point{
    double x, y;
    Point(){}
    Point(double x, double y) : x(x), y(y) {}
    bool operator <(const Point &rhs) const{
        return x < rhs.x;
    }
}s[N];
struct Circle{
    Point o;
    double r;
}a[N];
double sqr(double x){
    return x*x;
}
double getdis(Point a, Point b){
    return sqrt(sqr(b.x - a.x) + sqr(b.y - a.y));
}
void ins(double x, double y){
    s[++cnt] = Point(x, y);
}
void getsegment(Circle u, Circle v, double dis){
    double t1 = atan2(v.o.y - u.o.y, v.o.x - u.o.x);
    double t2 = acos((sqr(dis) + sqr(u.r) - sqr(v.r)) / (2*dis*u.r));
    Point t; t.x = t1 - t2; t.y = t1 + t2;
    if(t.x >= -pi && t.y <= pi) ins(t.x, t.y);
    else if(t.x < -pi){
        ins(t.x + 2*pi, pi);
        ins(-pi, t.y);
    }else{
        ins(t.x, pi);
        ins(-pi, t.y - 2*pi);
    }
}
double cal(){
    double ans = 0;
    double l = -10, r = -10;
    sort(s + 1, s + cnt + 1);
    for(int i = 1; i <= cnt; i++){
        if(s[i].x > r){
            ans += r - l;
            l = s[i].x;
            r = s[i].y;
        }
        else{
            r = max(r, s[i].y);
        }
    }
    ans += (r - l);
    return 2*pi - ans;
}
int main(){
    double ans = 0;
    scanf("%d", &n);
    for(int i = n; i >= 1; i--) scanf("%lf%lf%lf", &a[i].r, &a[i].o.x, &a[i].o.y);
    for(int i = 1; i <= n; i++){
        cnt = 0;
        int j;
        for(j = 1; j < i; j++){
            double tmp = getdis(a[i].o, a[j].o);
            if(a[j].r - a[i].r > tmp) break;
            if(a[j].r + a[i].r > tmp && fabs(a[j].r - a[i].r) < tmp){
                getsegment(a[i], a[j], tmp);
            }
        }
        if(j == i) ans += a[i].r * cal();
    }

    printf("%.3f\n", ans);
    return 0;
}

相關文章