简单计算几何

精度

  • 通常如果题目给定范围是\([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,

则有:

\[x\geq x1\\x\geq x2\\\cdots \\x\geq x_{i-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;
}
posted @ 2021-04-27 09:02  wwwsfff  阅读(113)  评论(0)    收藏  举报