简单计算几何
精度
-
通常如果题目给定范围是\([1,10^x]\),则往往应该\(eps\leq10^{-x},INF\geq 10^x\)
-
a>b<=>a>b+eps,a==b<=>fabs(b-a)<=eps(小心-0)
基础知识
点,线,向量的加减乘除,点乘叉乘的灵活运用,向量的旋转(借助复数)
判断点在线段的左右(叉积,旋转方向)
判断线段是否有交点(跨立实验),直线求交点,
判断点是否在多边形内部(给边定向,特判边上的点),
多边形面积(叉积)
圆和直线(圆)求交点
例题1
A. 【例题1】多边形内点 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
给边定向(上不取下取),从该点向左连出一条平行\(x\)轴射线,求交点个数奇偶性(图片见小本本)
即在左边(叉积),y包括
WA了一次:
特判在边上时,判断是否在线段上可以采用通法点乘\(\leq 0\),也可以特判\(x,y\),注意\(x,y\)都要,之前只特判了\(y\),平行就挂掉了
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n;
struct Po{int x,y;} a[N];
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline int operator *(Po i,Po j) {
return i.x*j.y-i.y*j.x;
}
inline int dot(Po i,Po j) {
return i.x*j.x+i.y*j.y;
}
inline bool check(Po u,Po v,Po p) {
if((u-p)*(v-p)!=0) return 0;
return (u.y<=p.y&&p.y<=v.y||v.y<=p.y&&p.y<=u.y)&&(u.x<=p.x&&p.x<=v.x||v.x<=p.x&&p.x<=u.x);//dot(u-p,v-p)<=0;
}
bool inner(Po t) {
bool ret=0;
for(int i=1,j=n;i<=n;j=i,i++) {
if(check(a[i],a[j],t)) return 1;
int d1=a[i].y-t.y,d2=a[j].y-t.y,det=(a[i]-t)*(a[j]-t);
if((det>=0)&&d1<=0&&d2>0||(det<=0&&d1>0&&d2<=0)) ret^=1;
}
return ret;
}
int main() {
int id=0;
while(scanf("%d",&n),n!=0) {
int T; scanf("%d",&T);
for(int i=1;i<=n;i++) {
scanf("%d%d",&a[i].x,&a[i].y);
}
printf("Problem %d: \n",++id);
while(T--) {
Po t; scanf("%d%d",&t.x,&t.y);
puts(inner(t)?"Within":"Outside");
}
puts("");
}
return 0;
}
例题2
B. 相交的直线 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
见小本本(跨立实验+求交点)
判重合可以头和尾剪一剪
#include<bits/stdc++.h>
#define db double
using namespace std;
const int N=105;
int n;
struct Po{db x,y;} a[N];
struct Li{Po s,t; } l1,l2;
inline Po operator +(Po i,Po j) {
return (Po){i.x+j.x,i.y+j.y};
}
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline db operator *(Po i,Po j) {
return i.x*j.y-i.y*j.x;
}
inline Po operator *(Po i,db j) {
return (Po){i.x*j,i.y*j};
}
inline db dot(Po i,Po j) {
return i.x*j.x+i.y*j.y;
}
inline Po XX(Li l1,Li l2) {
Po A=l1.s,B=l1.t,C=l2.s,D=l2.t;
db s1=(A-C)*(D-C),s2=(D-C)*(B-C);
return A+(B-A)*(s1/(s1+s2));
}
int main() {
int T; scanf("%d",&T);
puts("INTERSECTING LINES OUTPUT");
while(T--) {
scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&l1.s.x,&l1.s.y,&l1.t.x,&l1.t.y,&l2.s.x,&l2.s.y,&l2.t.x,&l2.t.y);
Po t1=l1.t-l1.s,t2=l2.t-l2.s;
if(t1*t2==0) {
t1=l2.t-l1.t;
if(t1*t2==0) {
puts("LINE");
} else {
puts("NONE");
}
} else {
Po ans=XX(l1,l2);
printf("POINT %.2lf %.2lf\n",ans.x,ans.y);
}
}
puts("END OF OUTPUT");
return 0;
}
例题3
C. 多边形求面积 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
相邻求叉积求和
#include<bits/stdc++.h>
#define db double
using namespace std;
const int N=105;
int n;
struct Po{db x,y;} a[N];
struct Li{Po s,t; } l1,l2;
inline Po operator +(Po i,Po j) {
return (Po){i.x+j.x,i.y+j.y};
}
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline db operator *(Po i,Po j) {
return i.x*j.y-i.y*j.x;
}
inline Po operator *(Po i,db j) {
return (Po){i.x*j,i.y*j};
}
inline db sum() {
if(n==1) return 0;
db ret=0;
for(int i=1,j=n;i<=n;j=i,i++) {
ret+=a[i]*a[j];
}
return abs(ret)/2;
}
int main() {
while(scanf("%d",&n),n) {
for(int i=1;i<=n;i++) {
scanf("%lf%lf",&a[i].x,&a[i].y);
}
printf("%.0lf\n",sum());
}
return 0;
}
例题4
E. 重叠的阴影 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
如果存在这么一条直线,则其在公共部分的垂线一定与所有线段相交,只需要判断是否存在这种线段即可
用极限的角度feel一下,如果存在,显然存在可以取任意两个点组成的线段
#include<bits/stdc++.h>
#define db double
const db eps=1e-9;
using namespace std;
const int N=205;
int n,m,ans[N];
struct Po{db x,y;} b[N];
struct Li{Po s,t; } a[N];
inline Po operator +(Po i,Po j) {
return (Po){i.x+j.x,i.y+j.y};
}
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline db operator *(Po i,Po j) {
return i.x*j.y-i.y*j.x;
}
inline bool equ(db x,db y) {
return abs(x-y)<=eps;
}
inline bool operator !=(Po i,Po j) {
return !equ(i.x,j.x)||!equ(i.y,j.y);
}
inline Po operator *(Po i,db j) {
return (Po){i.x*j,i.y*j};
}
bool XX(Li l1,Li l2) {
return ((l2.s-l1.s)*(l2.s-l1.t))*((l2.t-l1.s)*(l2.t-l1.t))<=eps;
}
bool check(Li l) {
for(int i=1;i<=n;i++) {
if(!XX(l,a[i])) return 0;
}
return 1;
}
int main() {
int T; scanf("%d",&T);
while(T--) {
scanf("%d",&n); m=0;
for(int i=1;i<=n;i++) {
scanf("%lf%lf%lf%lf",&a[i].s.x,&a[i].s.y,&a[i].t.x,&a[i].t.y);
b[++m]=a[i].s,b[++m]=a[i].t;
}
bool fl=0;
for(int i=1;i<=m;i++) {
for(int j=i+1;j<=m;j++) {
if(b[i]!=b[j]) {
if(check((Li){b[i],b[j]})) {
fl=1;
break;
}
}
}
if(fl) break;
}
puts(fl?"Yes!":"No!");
}
return 0;
}
例题5
F. 寻找宝藏 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
Feel一下:
- 在墙壁选点,如果要从那里出去,则与其连线后与该线相交的所有线都要被爆破
而极值在靠墙点取到,所以只需要枚举靠墙点即可
#include<bits/stdc++.h>
#define db double
const db eps=1e-9;
using namespace std;
const int N=205;
int n,m,ans[N];
struct Po{db x,y;} b[N];
struct Li{Po s,t; } a[N];
inline Po operator +(Po i,Po j) {
return (Po){i.x+j.x,i.y+j.y};
}
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline db operator *(Po i,Po j) {
return i.x*j.y-i.y*j.x;
}
inline bool equ(db x,db y) {
return abs(x-y)<=eps;
}
inline bool operator !=(Po i,Po j) {
return !equ(i.x,j.x)||!equ(i.y,j.y);
}
inline Po operator *(Po i,db j) {
return (Po){i.x*j,i.y*j};
}
bool XX(Li l1,Li l2) {
return (((l2.s-l1.s)*(l2.s-l1.t))*((l2.t-l1.s)*(l2.t-l1.t))<=eps)&&(((l1.s-l2.s)*(l1.s-l2.t))*((l1.t-l2.s)*(l1.t-l2.t))<=eps);
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%lf%lf%lf%lf",&a[i].s.x,&a[i].s.y,&a[i].t.x,&a[i].t.y);
}
Po t; scanf("%lf%lf",&t.x,&t.y);
int ans=n;
for(int i=1;i<=n;i++) {
Li l=(Li){a[i].s,t};
int sum=0;
for(int j=1;j<=n;j++) {
if(i!=j) {
if(XX(l,a[j])) sum++;
}
}
ans=min(ans,sum);
l=(Li){a[i].t,t};
sum=0;
for(int j=1;j<=n;j++) {
if(i!=j) {
if(XX(l,a[j])) sum++;
}
}
ans=min(ans,sum);
}
printf("Number of doors = %d\n",ans+1);
return 0;
}
例题6
G. 穿越管道 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
Feel一下:每条光线的极值点为一个上端点,一个下端点(两个上端点或者两个下端点都做也没关系,反正时间复杂度很低)
WA了一次:初值应该时-INF,而不是0
#include<bits/stdc++.h>
#define db double
const db eps=1e-9;
using namespace std;
const int N=205;
int n,m;
struct Po{db x,y;} a[N],b[N];
struct Li{Po s,t; } ;
inline Po operator +(Po i,Po j) {
return (Po){i.x+j.x,i.y+j.y};
}
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline db operator *(Po i,Po j) {
return i.x*j.y-i.y*j.x;
}
inline bool equ(db x,db y) {
return abs(x-y)<=eps;
}
inline bool operator !=(Po i,Po j) {
return !equ(i.x,j.x)||!equ(i.y,j.y);
}
inline Po operator *(Po i,db j) {
return (Po){i.x*j,i.y*j};
}
bool XX(Li l1,Li l2) {
return ((l2.s-l1.s)*(l2.s-l1.t))*((l2.t-l1.s)*(l2.t-l1.t))<=eps;
}
Po Crs(Li l1,Li l2) {
Po A=l1.s,B=l1.t,C=l2.s,D=l2.t;
db s1=abs((A-C)*(B-C)),s2=abs((A-D)*(B-D));
return C+(D-C)*(s1/(s1+s2));
}
int main() {
while(scanf("%d",&n),n) {
for(int i=1;i<=n;i++) {
scanf("%lf%lf",&a[i].x,&a[i].y);
b[i].x=a[i].x,b[i].y=a[i].y-1;
}
db ans=-1e9;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(i!=j) {
Li l;
if(i<j) l=(Li){a[i],b[j]};
else l=(Li){b[j],a[i]};
if(XX(l,(Li){b[1],a[1]})) {
for(int k=2;k<=n;k++) {
if((a[k]-a[k-1])*(l.t-l.s)==0) {
continue;
}
if(XX(l,(Li){a[k-1],a[k]})) {
db t=Crs(l,(Li){a[k-1],a[k]}).x;
ans=max(ans,t);
if(!equ(t,a[k].x)&&!equ(t,a[k-1].x)) break;
else if(equ(t,a[k-1].x)&&(l.t-l.s)*(a[k]-a[k-1])<eps){
break;
}
}
if(XX(l,(Li){b[k-1],b[k]})) {
db t=Crs(l,(Li){b[k-1],b[k]}).x;
ans=max(ans,t);
if(!equ(t,b[k].x)&&!equ(t,b[k-1].x)) {
break;
} else if(equ(t,b[k-1].x)&&(l.t-l.s)*(b[k]-b[k-1])>eps) {
break;
}
}
if(k==n) {
ans=1e9;
}
}
}
}
}
}
if(ans>=a[n].x) {
puts("Through all the pipe.");
} else {
printf("%.2lf\n",ans);
}
}
return 0;
}
例题7
H. 可视区间 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
在房子和道路中间的障碍物才有用
每个障碍物阻挡道路上的区间是一定的,所以变成了若干区间求并
set+map或者sort
WA了一次: 区间(l,r)可能不存在(l>max或者r<min)
#include<bits/stdc++.h>
#define db double
const db eps=1e-9;
using namespace std;
const int N=205;
int n,m;
struct Po{db x,y;} a[N],b[N];
struct Li{Po s,t; } ;
set<db>s;
unordered_map<db,int>mp;
int main() {
Po hom,rod; db y0,y1;
while(scanf("%lf%lf%lf",&hom.x,&hom.y,&y1),hom.x||hom.y||y1) {
scanf("%lf%lf%lf%d",&rod.x,&rod.y,&y0,&n);
mp.clear(),s.clear();
for(int i=1;i<=n;i++) {
Po t; db y; scanf("%lf%lf%lf",&t.x,&t.y,&y);
if(y0<=y&&y<=y1) {
t.x-=(hom.y-t.x)/(y1-y)*(y-y0);
t.y+=(t.y-hom.x)/(y1-y)*(y-y0);
t.x=max(t.x,rod.x);
t.y=min(t.y,rod.y);
if(t.x<t.y) {
mp[t.x]++,mp[t.y]--;
s.insert(t.x),s.insert(t.y);
}
}
}
db ans=0; db lst=rod.x; int sum=0;
for(auto i:s) {
if(sum<=0) {
ans=max(ans,i-lst);
sum+=mp[i];
} else {
if(sum>0) {
sum+=mp[i];
if(sum<=0) lst=i;
} else sum+=mp[i];
}
}
if(sum<=0) ans=max(ans,rod.y-lst);
if(ans==0) puts("No View");
else printf("%.2lf\n",ans);
}
return 0;
}
例题8
I. 圆的投影 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
光源和圆心连线,可以得到与其切线的夹角,从而得到切线,然后与y=0的交点
#include<bits/stdc++.h>
#define db double
const db INF=1e18;
using namespace std;
const int N=505;
int n,m;
struct Po{db x,y;} a[N],b[N];
struct Li{Po s,t; } ;
set<db>s;
unordered_map<db,int>mp;
inline db dis(Po i,Po j) {
return sqrt((i.x-j.x)*(i.x-j.x)+(i.y-j.y)*(i.y-j.y));
}
int main() {
Po g;
while(scanf("%d",&n),n) {
scanf("%lf%lf",&g.x,&g.y);
mp.clear(),s.clear();
for(int i=1;i<=n;i++) {
Po t; db r; scanf("%lf%lf%lf",&t.x,&t.y,&r);
db d=dis(t,g),aph=asin(r/d),bet=asin((t.x-g.x)/d);
t.x=g.x+g.y*tan(bet-aph);
t.y=g.x+g.y*tan(bet+aph);
if(t.x>t.y) swap(t.x,t.y);
mp[t.x]++,mp[t.y]--;
s.insert(t.x),s.insert(t.y);
}
db lst=-INF; int sum=0;
for(auto i:s) {
if(sum==0) {
sum+=mp[i];
if(sum>0) lst=i;
} else {
sum+=mp[i];
if(sum==0) {
printf("%.2lf %.2lf\n",lst,i);
}
}
}
puts("");
}
return 0;
}
例题9
J. 可视正方形 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
原题中给了一张图,每个正方形都是反转45度,不妨把对角线乘上\(\sqrt2\)
考虑\(1->{i-1}\)被放好,接下来放\(i\)
显然,可以枚举前面的正方形求出\(i\)与它们相交的最小的左端点的x坐标设为x1,x2,...,xi-1,
则有:
显然\(i\)的左端点x最小为max(x1,x2,...,xi-1)
即得到每个正方形的做右端点的范围,思考如何判断是否会被看见,显然只有大的遮蔽小的
所以从大到小排序,然后用set维护不相交的区间
WA了很多次:set维护时忘记了删中间的了
#include<bits/stdc++.h>
#define db double
const db INF=1e18;
using namespace std;
const int N=505;
int n,m,a[N],b[N],id[N];
bool cmp(int i,int j) {
return a[i]>a[j];
}
struct A{int l,r; };
bool operator <(A i,A j) {
return i.l<j.l;
}
set<A>s;
vector<A>V1;
vector<int>V;
int main() {
while(scanf("%d",&n),n) {
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
b[i]=0;
for(int j=1;j<i;j++) {
if(a[j]<a[i]) {
b[i]=max(b[i],b[j]+3*a[j]-a[i]);
} else {
b[i]=max(b[i],b[j]+a[i]+a[j]);
}
}
id[i]=i;
}
sort(id+1,id+n+1,cmp);
s.clear(); V.clear();
for(int i=1;i<=n;i++) {
A u=(A){b[id[i]],b[id[i]]+a[id[i]]+a[id[i]]};
auto t=s.upper_bound(u); V1.clear();
if(t!=s.begin()) {
t--;
if((t->r)>=u.l&&(t->r)<u.r) {
u.l=t->l; V1.push_back(*t);
}
}
t=s.upper_bound(u);
while(t!=s.end()&&(t->r)<=u.r) {
V1.push_back(*t);
t++;
}
if(t!=s.end()) {
if((t->l)<=u.r) {
V1.push_back(*t);
u.r=t->r;
}
}
for(auto i:V1) {
s.erase(i);
}
t=s.upper_bound(u);
if(t!=s.begin()) {
t--;
if((t->r)>=u.r) continue;
}
V.push_back(id[i]);
s.insert(u);
}
sort(V.begin(),V.end());
for(int i=0;i<V.size();i++) {
printf("%d%c",V[i],i+1==V.size()?'\n':' ');
}
}
return 0;
}
例题10
K. 雨水收集器 - 「计算几何」第1章 计算几何初探 - 金牌导航 - 课程 - YbtOJ
需要特判很多种0的情况(见小本本)
其中前2种不用特判,第3种情况是真的难想
WA了一次:没特判第3种
#include<bits/stdc++.h>
#define db double
const db INF=1e9;
const db eps=1e-9;
using namespace std;
struct Po{db x,y; };
struct Li{Po s,t; }l1,l2;
inline Po operator +(Po i,Po j) {
return (Po){i.x+j.x,i.y+j.y};
}
inline Po operator -(Po i,Po j) {
return (Po) {i.x-j.x,i.y-j.y};
}
inline Po operator *(Po i,db j) {
return (Po){i.x*j,i.y*j};
}
inline db operator *(Po i,Po j) {
return i.x*j.y-i.y*j.x;
}
inline bool equ(db i,db j) {
return abs(j-i)<=eps;
}
bool XX(Li l1,Li l2) {
Po A=l1.s,B=l1.t,C=l2.s,D=l2.t;
return (((A-C)*(B-C))*((A-D)*(B-D))<=eps&&((C-A)*(D-A))*((C-B)*(D-B))<=eps);
}
Po Crs(Li l1,Li l2) {
Po A=l1.s,B=l1.t,C=l2.s,D=l2.t;
db s1=abs((A-C)*(B-C)),s2=abs((A-D)*(B-D));
return C+(D-C)*(s1/(s1+s2));
}
int main() {
int T; scanf("%d",&T);
while(T--) {
scanf("%lf%lf%lf%lf%lf%lf%lf%lf",&l1.s.x,&l1.s.y,&l1.t.x,&l1.t.y,&l2.s.x,&l2.s.y,&l2.t.x,&l2.t.y);
if(!XX(l1,l2)||equ((l1.t-l1.s)*(l2.t-l2.s),0)||equ(l1.s.y,l1.t.y)||equ(l2.s.y,l2.t.y)) {
puts("0.00"); continue;
}
Po mid=Crs(l1,l2);
if(l1.t.y>mid.y) swap(l1.t,l1.s);
if(l2.t.y>mid.y) swap(l2.t,l2.s);
l1.t=mid,l2.t=mid;
if(l1.s.y>l2.s.y) swap(l1,l2);
if(XX((Li){l2.s,mid},(Li){l1.s,(Po){l1.s.x,INF}})){
puts("0.00"); continue;
}
if(equ(l1.s.y,l1.t.y)||equ(l2.s.y,l2.t.y)) {
puts("0.00"); continue;
}
Po t1=l1.s,t2=l2.s;
if(t1.y>t2.y) swap(t1,t2);
t1=t1-mid,t2=(t2-mid)*(t1.y/(t2-mid).y);
printf("%.2lf\n",abs(t1*t2*0.5));
}
return 0;
}
例题11
[P3630 APIO2010]信号覆盖 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
暴力:枚举3个点,枚举第4个点,判断是否在3个点组成的圆内
所以可以枚举4个点,判断4个点中有几组
由四点共圆的知识可以得到:如果某3个点构成一个圆,则第四个点和其对应的点的角度和$>$180
-
如果是凸四边形,则内角和只有360度,有且只有1组,也就是2个
-
如果是凹四边形,则只有1个
而4个点不是凸就是凹,所以只需要枚举一种,然后用\(C_{n}^{4}\)取减
显然枚举凹的比较好,枚举中间那个点,再枚举一个端点,则剩余两个很难算,不妨再用容斥,发现如果是凸的,则剩余3个点在同一侧,所以用\(C_{n-1}^{3}\)-在一侧的点数
PS:不能直接计算凸的
#include<bits/stdc++.h>
#define db long double
const db Pi=acos(-1);
using namespace std;
const int N=3005;
int n,m;
db b[N];
struct Po{db x,y; }a[N];
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline db Atan(Po t) {
return atan2(t.y,t.x);
}
inline db C(db n,int m) {
db ret=1;
for(int i=1;i<=m;i++) {
ret=ret*(n-i+1)/i;
}
return ret;
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%Lf%Lf",&a[i].x,&a[i].y);
}
db ans=0;
for(int i=1;i<=n;i++) {
m=0;
for(int j=1;j<=n;j++) {
if(j!=i) {
b[++m]=Atan(a[j]-a[i]);
}
}
sort(b+1,b+m+1); db sum=C(m,3);
for(int j=m+1;j<=m+m;j++) {
b[j]=b[j-m]+Pi*2;
}
for(int k=m+1,j=2;k<=m+m;k++) {
while(j<k&&b[k]-b[j]>Pi) j++;
if(k-j>=2) sum-=C(k-j,2);
}
ans+=sum;
}
ans=C(n,4)-ans+C(n,4);
printf("%.3Lf\n",ans/C(n,3)+3);
return 0;
}
例题12
[P2510 HAOI2008]下落的圆盘 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
从后往前枚举,枚举后面的圆和当前的交的部分(显然是一段圆弧,可以用圆心角区间表示),然后求没被覆盖的部分的面积,直接set+map维护即可
WA了好几次:没有考虑被后面完全包含的情况;或者考虑了却把前面覆盖后面的也考虑进来了。。
#include<bits/stdc++.h>
#define db double
const db Pi=acos(-1);
using namespace std;
const int N=1005;
int n;
db r[N];
struct Po {db x,y; }a[N];
inline Po operator -(Po i,Po j) {
return (Po){i.x-j.x,i.y-j.y};
}
inline Po operator /(Po i,db j) {
return (Po){i.x/j,i.y/j};
}
inline db dis(Po i,Po j) {
return sqrt((i.x-j.x)*(i.x-j.x)+(i.y-j.y)*(i.y-j.y));
}
set<db>s;
unordered_map<db,int>mp;
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%lf%lf%lf",&r[i],&a[i].x,&a[i].y);
}
db ans=0;
for(int i=n;i;i--) {
s.clear(),mp.clear();
for(int j=i+1;j<=n;j++) {
db d=dis(a[i],a[j]);
if(abs(r[i]-r[j])<d&&d<r[i]+r[j]) {
db aph=abs(acos((r[i]*r[i]+d*d-r[j]*r[j])/(2*r[i]*d)));
Po t=(a[j]-a[i])/d;
db ang=atan2(t.y,t.x),R=ang+aph,L=ang-aph;
if(R>Pi) {
mp[-Pi]++,mp[R-2*Pi]--;
s.insert(-Pi),s.insert(R-2*Pi);
R=Pi;
}
if(L<-Pi) {
mp[L+2*Pi]++,mp[Pi]--;
s.insert(L+2*Pi),s.insert(Pi);
L=-Pi;
}
mp[L]++,mp[R]--;
s.insert(L),s.insert(R);
} else if(d<=r[j]-r[i]) {
mp[-Pi]++,mp[Pi]--;
s.insert(-Pi),s.insert(Pi);
}
}
db lst=-Pi,ret=0;int sum=0;
for(db i:s) {
if(sum<=0) {
ret+=i-lst;
sum+=mp[i];
} else {
sum+=mp[i];
if(sum==0) {
lst=i;
}
}
}
if(sum==0) ret+=Pi-lst;
ans+=ret*r[i];
}
printf("%.3lf\n",ans);
return 0;
}