[bzoj] 1043 下落的圆盘 || 圆上的“线段覆盖”

原题

n个圆盘,求下落后能看到的总周长。

红色即为所求


借鉴于黄学长的博客
对于每下落的一个圆盘,处理他后面的圆盘会挡住哪些区域,然后把一整个圆(2\(/pi\))当做一整个区间,每个被覆盖的部分都可以化为一条线段,做线段覆盖就可以得到最后这个圆对答案的贡献了。

详解见代码。

#include<cstdio>
#include<algorithm>
#include<cmath>
#define pi acos(-1)
#define N 1010
typedef long long ll;
using namespace std;
int n,top;
double ans,x[N],y[N],r[N];
struct line
{
    double l,r;
    line() {}
    line(double x,double y) : l(x),r(y) {}
    line(int x,double y) : l(x),r(y) {}//鬼畜的构造函数……
    bool operator < (const line b) const
	{
	    return l<b.l;
	}
}q[N];

inline double dis(int a,int b)
{
    return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}

bool conta(int a,int b)//判断b圆有没有被a圆完全覆盖
{
    if (r[a]>=r[b]+dis(a,b)) return 1;
    return 0;
}

void inter(int a,int b)
{
    double d,t,st,l;
    d=dis(a,b);
    t=(r[a]*r[a]-r[b]*r[b]+d*d)/(2*d*r[a]);//余弦定理求角(位置见上图)
    st=atan2((x[a]-x[b]),(y[a]-y[b]));
    l=acos(t);
    q[++top]=line(st-l,st+l);//以弧的两个端点当做线段的两个端点
}

double cal(int x)//求第x个圆最后能看到的周长
{
    for (int i=x+1;i<=n;i++)
	if (conta(i,x)) return 0;//如果被其他圆覆盖,就没有贡献
    top=0;
    for (int i=x+1;i<=n;i++)
	if (!conta(x,i) && r[x]+r[i]>=dis(x,i))//这两个圆相交
	    inter(x,i);//求出被覆盖的部分并简化为线段
    double tmp=0,now=0;
    for (int i=1;i<=top;i++)//把角度都处理到[0,$2/pi$)中
    {
	if (q[i].l<0) q[i].l+=2*pi;
	if (q[i].r<0) q[i].r+=2*pi;
	if (q[i].l>q[i].r)
	{
	    q[++top]=line(0,q[i].r);
	    q[i].r=2*pi;
	}
    }
    sort(q+1,q+top+1);
    for (int i=1;i<=top;i++)//线段覆盖
	if (q[i].l>now)
	{
	    tmp+=q[i].l-now;
	    now=q[i].r;
	}
	else now=max(now,q[i].r);
    tmp+=2*pi-now;
    return r[x]*tmp;//能看到的长度
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
	scanf("%lf%lf%lf",&r[i],&x[i],&y[i]);
    for (int i=1;i<=n;i++)
	ans+=cal(i);
    printf("%.3f",ans);
    return 0;
}
posted @ 2017-12-26 15:32  Mrha  阅读(250)  评论(0编辑  收藏  举报