[BZOJ 1043][HAOI 2008]下落的圆盘(计算几何)

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1043

思路

实际上我们可以对每个圆盘分开来看,对于每个圆盘,看它被之后放上去的圆盘遮住了多少长度,然后就能得到这个圆盘最终露出的长度,累加答案即可。
那么我们可以把一个圆盘和另一个圆盘相交的那段圆弧的两段的半径的极角看成是一个区间的左右端点,并保证端点的区间是在 [0,2π] 。对于一个圆盘,我们得到它和它之后的圆盘相交的所有圆弧对应的区间,实际上之后要做的事情就是求这些区间的区间并,这是个很简单的贪心问题,相信大家都会,蒟蒻我就不说了。

虽然看起来嘴巴两下还是比较简单的,但是还是有很多细节需要注意,否则会WA得很惨。。。比如说判断两个圆是否相交,还有如果当前的圆被其之后放置的圆给完全遮住了,就直接忽略掉这个圆即可。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <cmath>

#define MAXN 1100
#define PI 3.1415926535897384626

using namespace std;

double ans=0;

struct Point
{
    double x,y;
    Point(){}
    Point(double _x,double _y):x(_x),y(_y){}
};

Point operator-(Point a,Point b)
{
    return Point(a.x-b.x,a.y-b.y);
}

struct Circle
{
    Point o; //圆心
    double r; //半径
    Circle(){}
    Circle(Point _o,double _r):o(_o),r(_r){}
}circles[MAXN];

struct Interval //区间,把弧的两端的半径的极角看作是区间端点
{
    double L,R;
    Interval(){}
    Interval(double _L,double _R):L(_L),R(_R){}
}intervals[MAXN*MAXN];

int tot=0; //区间个数

bool cmp(Interval a,Interval b)
{
    if(a.L==b.L) return a.R<b.R;
    return a.L<b.L;
}

double getang(Point a) //求向量a的极角
{
    return atan2(a.y,a.x);
}

double eucliddist(Point a,Point b) //求点a、b的距离
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

void calcInterval(Circle a,Circle b,double dist) //求圆b盖在圆a上的圆弧区间
{
    double alpha=getang(b.o-a.o)+PI; //!!!!!!!两个圆心连线的极角
    double delta=acos((a.r*a.r+dist*dist-b.r*b.r)/(2*a.r*dist));
    double newL=alpha-delta,newR=alpha+delta; //新加入极角区间为[newL,newR]
    if(newL>=0&&newR<=2*PI)
        intervals[++tot]=Interval(newL,newR);
    else if(newL<0)
        intervals[++tot]=Interval(newL+2*PI,2*PI),intervals[++tot]=Interval(0,newR);
    else
        intervals[++tot]=Interval(newL,2*PI),intervals[++tot]=Interval(0,newR-2*PI);
}

double IntervalUnion() //合并当前的intervals数组中的所有区间,得到最终当前的圆剩下的周长
{
    double sum=0;
    sort(intervals+1,intervals+tot+1,cmp);
    double start=0,end=0; //当前的连续一段区间并是[start,end]
    for(int i=1;i<=tot;i++)
    {
        if(intervals[i].L>end)
        {
            sum+=end-start;
            start=intervals[i].L; //!!!!!!!!
            end=intervals[i].R;
        }
        else
            end=max(end,intervals[i].R);
    }
    sum+=end-start;
    return 2*PI-sum;
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lf%lf%lf",&circles[i].r,&circles[i].o.x,&circles[i].o.y);
    for(int i=1;i<=n;i++) //求第i个圆最终没被覆盖的部分的长度
    {
        bool flag=true; //flag=false表示圆i被后面的圆完全覆盖住了
        tot=0;
        for(int j=i+1;j<=n;j++)
        {
            double dist=eucliddist(circles[i].o,circles[j].o);
            if(circles[j].r-circles[i].r>dist) //圆i被之后的圆j完全盖住了
            {
                flag=false;
                break;
            }
            if(circles[i].r+circles[j].r>dist&&fabs(circles[i].r-circles[j].r)<dist) //!!!!!有相交部分
                calcInterval(circles[i],circles[j],dist);
        }
        if(flag) ans+=IntervalUnion()*circles[i].r;
    }
    printf("%.3lf\n",ans);
    return 0;
}

你可能感兴趣的:([BZOJ 1043][HAOI 2008]下落的圆盘(计算几何))