BZOJ 1043: [HAOI2008]下落的圆盘
1043: [HAOI2008]下落的圆盘
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 1533 Solved: 644
[Submit][Status][Discuss]
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
题解
n<=1000,n^2可做,按顺序枚举每个圆,再依次枚举之后的所有圆,用数组维护圆上被覆盖的弧度范围,最后类似线段覆盖求出覆盖总和,就可以算出最后可见的长度。
细节:
1.如果当前圆被后面某个圆完全覆盖,那么不考虑和其他圆相交。
2.覆盖的弧度范围要处理一下,如果弧度<0,那么弧度+2π,如果加了之后l>r,那么将l-r拆成0-r和l-2π两段。
代码
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<iostream> using namespace std; const int N=1005; const double pi=acos(-1.0); int n,top; double ans; struct stack{ double l,r; }st[N]; struct cir{ double r,x,y; }c[N]; double sqr(double a){ return a*a; } double dis(cir a,cir b){ return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y)); } bool cover(cir a,cir b){ if(a.r>=b.r+dis(a,b))return true; return false; } bool cross(cir a,cir b){ if(a.r+b.r>dis(a,b))return true; return false; } void work(cir a,cir b){ double dist=dis(a,b); double ang=acos((sqr(a.r)+sqr(dist)-sqr(b.r))/(2*a.r*dist)); double std=atan2((b.y-a.y),(b.x-a.x)); st[++top]=(stack){std-ang,std+ang}; } bool cmp(stack a,stack b){ return a.l==b.r?a.r<b.r:a.l<b.l; } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%lf%lf%lf",&c[i].r,&c[i].x,&c[i].y); } int fg; double l,r; for(int i=1;i<=n;i++){ fg=0; for(int j=i+1;j<=n;j++){ if(cover(c[j],c[i])){ fg=1; break; } } if(fg)continue; top=0; for(int j=i+1;j<=n;j++){ if(cross(c[i],c[j])&&!cover(c[i],c[j]))work(c[i],c[j]); } for(int j=1;j<=top;j++){ while(st[j].l<0)st[j].l+=2*pi; while(st[j].r<0)st[j].r+=2*pi; if(st[j].l>st[j].r){ st[++top]=(stack){0,st[j].r}; st[j].r=2*pi; } } st[++top]=(stack){2*pi,2*pi}; sort(st+1,st+top+1,cmp); l=r=0; for(int j=1;j<=top;j++){ if(st[j].l<r){ if(st[j].r>r)r=st[j].r; continue; } ans-=(r-l)*c[i].r; l=st[j].l; r=st[j].r; } ans+=2*pi*c[i].r; } printf("%.3lf\n",ans); return 0; }