现在 n ( 1 ≤ n ≤ 1000 ) n(1\le n\le 1000) n(1≤n≤1000) 个圆依次放在桌上,每个圆的圆心坐标和半径已知,已知其从下向上的顺序。现在从上向下看,求所有可见圆的可见边的长度和。
对于每一个圆,枚举每个在它上方的圆,记录下被覆盖的部分(用极角保存),然后对被覆盖的部分取并减去再加上原周长即可。
两个圆的关系分情况讨论:
下面只讨论相交的做法

如上图,在
△
A
B
C
\triangle ABC
△ABC 中,
A
C
AC
AC 为圆
A
A
A 的半径,
B
C
BC
BC 为圆
B
B
B 的半径,
A
B
AB
AB 为圆心间的距离,均易求得,因而可由余弦定理求得
∠
B
A
D
=
∠
B
A
C
=
A
B
2
+
A
C
2
−
B
C
2
2
A
B
⋅
A
C
\angle BAD=\angle BAC=\frac{AB^2+AC^2-BC^2}{2AB·AC}
∠BAD=∠BAC=2AB⋅ACAB2+AC2−BC2 。与水平线的夹角
∠
B
A
E
\angle BAE
∠BAE 可由反正切函数求得(传入直线AB的斜率即可)。
#include
using namespace std;
template<class T>inline void read(T&x){
char c,last=' ';
while(!isdigit(c=getchar()))last=c;
x=c^48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
if(last=='-')x=-x;
}
#define sqr(x) ((x)*(x))
const double pi=acos(-1);
const int MAXN=1e3+5;
int n;
double r[MAXN],x[MAXN],y[MAXN];
double dis(int i,int j){//求第i,j个圆心间的距离
return sqrt(sqr(x[i]-x[j])+sqr(y[i]-y[j]));
}
int main()
{
read(n);
for(int i=1;i<=n;++i)cin>>x[i]>>y[i]>>r[i];
double ans=0;
for(int i=1;i<=n;++i){
vector<pair<double,double> >v;
for(int j=i+1;j<=n;++j){
if(dis(i,j)>=r[i]+r[j])continue;//外离
if(dis(i,j)<=abs(r[i]-r[j])){//包含上面的圆
if(r[j]>r[i])v.push_back(make_pair(0,2*pi));//被上面的圆包含,即完全覆盖
continue;
}
double dist=dis(i,j);
double delta=acos((sqr(r[i])+sqr(dist)-sqr(r[j]))/(2*r[i]*dist));//余弦定理求角
double base=atan2(y[j]-y[i],x[j]-x[i])+pi;//圆心连线的极角
double l=base-delta,r=base+delta;//两个边界
if(l<0){//l<0
v.push_back(make_pair(0,r));
v.push_back(make_pair(l+2*pi,2*pi));
}
else if(r>2*pi){//l<2*pi
v.push_back(make_pair(l,2*pi));
v.push_back(make_pair(0,r-2*pi));
}
else v.push_back(make_pair(l,r));//区间完整,直接存入
}
ans+=2*pi*r[i];
if(v.empty())continue;
sort(v.begin(),v.end());//pair自动优先按照第一关键字(即l)排序,然后按照第二关键字(即r)进行排序
double L=v[0].first,R=v[0].second;
for(int j=1;j<v.size();++j){
if(R<v[j].first){//当下一段区间与前面的区间断开时,直接结算前面的答案
ans-=(R-L)*r[i];
L=v[j].first,R=v[j].second;
}
else L=min(L,v[j].first),R=max(R,v[j].second);//直接并下一段区间
}
ans-=(R-L)*r[i];//剩余的未结算区间
}
cout<<fixed<<setprecision(12)<<ans<<'\n';
return 0;
}