BZOJ 1043: [HAOI2008]下落的圆盘 计算几何,贪心,线段交

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值