Codeforces372E Drawing Circles is Fun【圆的反演】

mac2025-12-03  15

题目描述:

平面n个点,原点O(0,0),不存在 P i , P j P_i,P_j Pi,Pj满足 P i O P j P_iOP_j PiOPj三点共线。 求一种集合的个数: ( P 1 , P 2 ) ( P 3 , P 4 ) . . . ( P 2 k − 1 , P 2 k ) (P_1,P_2)(P_3,P_4)...(P_{2k-1},P_{2k}) (P1,P2)(P3,P4)...(P2k1,P2k)(无序) 使得:

k ≥ 2 , P i ≠ P j k\ge 2,P_i\neq P_j k2Pi=Pj ∀ i ≠ j \forall i\neq j i=j,满足 △ O P 2 i − 1 P 2 j − 1 △OP_{2i-1}P_{2j-1} OP2i1P2j1的外接圆与 △ O P 2 i P 2 j △OP_{2i}P_{2j} OP2iP2j的外接圆只有一个交点, △ O P 2 i P 2 j − 1 △OP_{2i}P_{2j-1} OP2iP2j1的外接圆与 △ O P 2 i − 1 P 2 j △OP_{2i-1}P_{2j} OP2i1P2j的外接圆只有一个交点。

题目分析:

一个交点,想一想可以看出应该是圆心在一条直线上,可是并没有什么用,因为并不能快速求出所有点对的外接圆圆心以及判断是否在一条线上。

做这道题前需要先学习一波圆的反演:ACdreamers,NineSwords,Dance Of Faith 反演变换的几何证明 再结合GeoGebra画画图(有反演功能)应该就可以大概理解了。 反演圆的半径 r 2 = r 1 ∗ r 2 d 1 2 − r 1 2 r_2=\frac {r_1*r^2}{d_1^2-r_1^2} r2=d12r12r1r2,原象和反象是位似的。

对于这道题,将圆反演后只有一个交点就变为了两条直线平行,将每个点反演后的点记为 A i A_i Ai,问题则变为对 ∀ i ≠ j \forall i\neq j i=j,满足 A 2 i − 1 A 2 j − 1 ∥ A 2 i A 2 j A_{2i-1}A_{2j-1}\parallel A_{2i}A_{2j} A2i1A2j1A2iA2j A 2 i A 2 j − 1 ∥ A 2 i − 1 A 2 j A_{2i}A_{2j-1}\parallel A_{2i-1}A_{2j} A2iA2j1A2i1A2j。发现 A 2 i − 1 A 2 i A 2 j − 1 A 2 j A_{2i-1}A_{2i}A_{2j-1}A_{2j} A2i1A2iA2j1A2j是平行四边形,而平行四边形对顶点的中点相同,所以我们可以枚举点对然后记录它们反演后的中点和直线斜率(外接圆相同时斜率相同,但是不符合条件,斜率不同才可以计算),分开计算每个中点的答案然后乘起来即可,注意中点相同斜率相同的只能取一个。

Code:

#include<bits/stdc++.h> #define maxn 1005 using namespace std; const double eps = 1e-9; const int mod = 1e9+7; int n,m,ans; inline int sgn(double x){return (x>eps)-(x<-eps);} struct Point{ double x,y; Point(){} Point(double x,double y):x(x),y(y){} Point operator + (Point p)const{return Point(x+p.x,y+p.y);} Point operator * (double t)const{return Point(x*t,y*t);} }p[maxn]; struct node{ double x,y,k; bool operator < (const node &p)const{return sgn(x-p.x)?x<p.x:sgn(y-p.y)?y<p.y:k<p.k;} }M[maxn*maxn]; int main() { scanf("%d",&n); for(int i=1,a,b,c,d;i<=n;i++){ scanf("%d%d%d%d",&a,&b,&c,&d),p[i]=Point(1.*a/b,1.*c/d); p[i]=p[i]*(1/(p[i].x*p[i].x+p[i].y*p[i].y)); } for(int i=1;i<n;i++) for(int j=i+1;j<=n;j++) M[++m]=(node){(p[i].x+p[j].x)*0.5,(p[i].y+p[j].y)*0.5,sgn(p[i].x-p[j].x)?(p[i].y-p[j].y)/(p[i].x-p[j].x):1e9}; sort(M+1,M+1+m); for(int i=1,j;i<=m;i=j){ int cnt=1,ret=1; for(j=i+1;j<=m&&!sgn(M[j].x-M[i].x)&&!sgn(M[j].y-M[i].y);j++) if(!sgn(M[j].k-M[j-1].k)) cnt++; else ret=1ll*ret*(cnt+1)%mod,cnt=1;//cnt记录中点和斜率都相同的点对数目,每一组可以不选 ret=1ll*ret*(cnt+1)%mod; ans=(ans+ret-1)%mod;//可以不选这个中点 } printf("%d\n",(ans-m+mod)%mod);//可能只选了一个点对 }

​ .

最新回复(0)