一、两圆位置关系
两圆间位置关系有五种,相离,外切,相交,内切,内含,利用圆心距d和半径差以及半径和的大小关系来判定。
二、三角形内切圆
三角形内切圆的圆心是三条角平分线的交点,因此可以转化为求直线交点问题,任取两角平分线交点即为圆心,得到圆心后利用叉乘求出圆心到边的距离就是半径。
在引入向量这个概念后求角平分线也十分简单,先求两条边的向量,然后全部单位化,最后作向量加和就是角平分线的向量。
三、三角形外接圆
三角形外接圆的圆心是三条垂直平分线的交点,同样转为求两直线交点问题,因此要先求出至少两条垂直平分线。垂直平分线的起点很容易获得,就是边上的中点,然后需要将对应边向量旋转90°后得到垂向量,垂直平分线的终点坐标就是起点加该垂向量。得到两条垂直平分线后求个交点就是外接圆的圆心,最后用两点间距离公式求出半径。
四、直线和圆的交点
首先要知道给定的直线不一定和圆有交点,需要先用圆心到直线距离和半径比对一下,若该距离大于半径那根本不存在交点,直接退出就行。因此只考虑存在交点时的情况,如下图所示:
直线l与圆O的交点为A和B,过圆心O引一条AB的垂线,交AB于点P。根据现有条件可以求出点到直线距离OP的长度,进而得到AP长度。由于直线l是已知的,那么向量BA也是已知的,向量BA和向量PA是同向的,只是长度不同,因此向量PA也可以求出。将向量BA旋转90°可得到OP的同向向量,又知道OP长度,所以向量OP也已知。这时候向量OA就已知了,它等于OP+PA。然后O点坐标加向量OA得到A点坐标,B点坐标也是同理求出。
五、两圆交点
当两圆相离或者内含时是不存在交点的,所以只考虑剩下的相交或相切的情况。如下图所示,两圆交点为A和B,连接两圆心交AB于点P,显然AB垂直于O1O2。
设圆心距为d,O1P长度为x,则PO2长度为d-x,用勾股定理列出图中公式,解得x值,同时AP长度也已知。向量O1P是O1O2同向向量,又知道O1P长度,可以得到向量O1P,同理得到向量PA,最终得到向量O1A,这样就求出A点坐标了。B点坐标同理求得。
六、过定点圆的切线
依然是有一些特殊情况不用处理,当定点在圆内时不存在切线,所以只考虑点在圆外的情况,如下图所示。
PA切圆O于点A,PB切圆O于点B,AB交PO于点Q,显然PO垂直于AB。设QO长度为x,三角形AQO相似于三角形PAO,于是可以求出x值。之后的步骤和上面几个问题处理方式一致,求出向量OQ和向量QA,然后得到向量OA,这样就求出A点坐标了。
七、两圆公切线
这个问题是比较复杂的,情况比较多,首先要知道两圆相离时有4条公切线,外切时有3条,相交时有2条,内切时有1条,内含时有0条,因此要根据两圆位置关系来分类讨论。
以两圆相离时为例,其他情况都是类似的。
先求两条外切线,如下图所示。
有一个很有用的结论:两条外切线交点一定在两圆心连线上。如上图,点P一定在O1O2上。如果求出点P坐标那这个问题就可以转为六、过定点圆的切线问题了。如果把上一个问题实现过程封装到函数里,在解决这个问题时调用几次函数就可以了。
接下来考虑怎么求P点坐标,显然图中有一组相似三角形,之后得到比例关系:A2O2/A1O1 = O2P/O1P,从而求出O2P的长度,这样就得到向量O2P了,也就得到了点P坐标。
对于两条内切线步骤和上面基本一致,也是先找到两条切线交点P,然后转为六、过定点圆的切线问题,求P点坐标时也是发现一组相似三角形利用比例关系求得。
八、圆和多边形面积交
类似求多边形面积的方法,将多边形面积转化为若干个有向三角形的面积和,之后求每个有向三角形和圆的面积交,最后加和就是答案。
九、两圆相交面积
同样存在一些特殊情况,两圆相离或外切时面积为0,内切或内含时面积为较小的圆面积,所以只考虑相交时的情况,如下图所示。
两圆交点分别为A和B,连接AB交O1O2于点P,显然O1O2垂直于AB,待求面积为紫色区域。AB将紫色区域分为两部分,可以依次求每部分的面积,对于每部分面积就是扇形面积减去三角形面积。之后要做的就是求出各点坐标了,这个问题其实就是五、两圆交点,得到交点坐标后各边长就已知了,用初中数学知识解决即可。
十、模板
代码基于kuangbin的计算几何模板,又添加了一些我自己的理解,同时添加了求两圆公切线的函数。
//圆
struct circle{
Point p;//圆心
double r;//半径
circle(){}
circle(Point _p,double _r){
p = _p;
r = _r;
}
circle(double x,double y,double _r){
p = Point(x,y);
r = _r;
}
//`三角形的外接圆`
//`需要Point的+ / rotate() 以及Line的crosspoint()`
//`利用两条边的中垂线得到圆心`
//`测试:UVA12304`
circle(Point a,Point b,Point c){
Line u = Line((a+b)/2,((a+b)/2)+((b-a).rotleft()));
Line v = Line((b+c)/2,((b+c)/2)+((c-b).rotleft()));
p = u.crosspoint(v);
r = p.distance(a);
}
//`三角形的内切圆`
//`参数bool t没有作用,只是为了和上面外接圆函数区别`
//`测试:UVA12304`
circle(Point a,Point b,Point c,bool t){
Line u,v;
double m = atan2(b.y-a.y,b.x-a.x), n = atan2(c.y-a.y,c.x-a.x);
u.s = a;
u.e = u.s + Point(cos((n+m)/2),sin((n+m)/2));
v.s = b;
m = atan2(a.y-b.y,a.x-b.x) , n = atan2(c.y-b.y,c.x-b.x);
v.e = v.s + Point(cos((n+m)/2),sin((n+m)/2));
p = u.crosspoint(v);
r = Line(a,b).dispointtoseg(p);
}
//输入
void input(){
p.input();
scanf("%lf",&r);
}
//输出
void output(){
printf("%.2lf %.2lf %.2lf\n",p.x,p.y,r);
}
bool operator == (circle v){
return (p==v.p) && sgn(r-v.r)==0;
}
bool operator < (circle v)const{
return ((p<v.p)||((p==v.p)&&sgn(r-v.r)<0));
}
//面积
double area(){
return pi*r*r;
}
//周长
double circumference(){
return 2*pi*r;
}
//`点和圆的关系`
//`0 圆外`
//`1 圆上`
//`2 圆内`
int relation(Point b){
double dst = b.distance(p);
if(sgn(dst-r) < 0)return 2;
else if(sgn(dst-r)==0)return 1;
return 0;
}
//`线段和圆的关系`
//`比较的是圆心到线段的距离和半径的关系`
int relationseg(Line v){
double dst = v.dispointtoseg(p);
if(sgn(dst-r) < 0)return 2;
else if(sgn(dst-r) == 0)return 1;
return 0;
}
//`直线和圆的关系`
//`比较的是圆心到直线的距离和半径的关系`
int relationline(Line v){
double dst = v.dispointtoline(p);
if(sgn(dst-r) < 0)return 2;
else if(sgn(dst-r) == 0)return 1;
return 0;
}
//`两圆的关系`
//`5 相离`
//`4 外切`
//`3 相交`
//`2 内切`
//`1 内含`
//`需要Point的distance`
//`测试:UVA12304`
int relationcircle(circle v){
double d = p.distance(v.p);
if(sgn(d-r-v.r) > 0)return 5;
if(sgn(d-r-v.r) == 0)return 4;
double l = fabs(r-v.r);
if(sgn(d-r-v.r)<0 && sgn(d-l)>0)return 3;
if(sgn(d-l)==0)return 2;
if(sgn(d-l)<0)return 1;
}
//`求两个圆的交点,返回0表示没有交点,返回1是一个交点,2是两个交点`
//可能需要特判两圆完全重合
//`需要relationcircle`
//`测试:UVA12304`
int pointcrosscircle(circle v,Point &p1,Point &p2){
int rel = relationcircle(v);
if(rel == 1 || rel == 5)return 0;
double d = p.distance(v.p);//圆心距
double l = (d*d+r*r-v.r*v.r)/(2*d);//两次勾股定理求出圆心到两交点连线距离
double h = sqrt(r*r-l*l);//勾股定理求出两交点距离一半
Point tmp = p + (v.p-p).trunc(l);//两圆心连线与两交点连线的交点
p1 = tmp + ((v.p-p).rotleft().trunc(h));//向量左侧的交点
p2 = tmp + ((v.p-p).rotright().trunc(h));//向量右侧的交点
if(rel == 2 || rel == 4)
return 1;
return 2;
}
//`求直线和圆的交点,返回交点个数`
//关键是找到圆心在直线上的投影点
int pointcrossline(Line v,Point &p1,Point &p2){
if(!(*this).relationline(v))return 0;
Point a = v.lineprog(p);
double d = v.dispointtoline(p);
d = sqrt(r*r-d*d);
if(sgn(d) == 0){
p1 = a;
p2 = a;
return 1;
}
p1 = a + (v.e-v.s).trunc(d);
p2 = a - (v.e-v.s).trunc(d);
return 2;
}
//`得到过a,b两点,半径为r1的两个圆`
int gercircle(Point a,Point b,double r1,circle &c1,circle &c2){
circle x(a,r1),y(b,r1);
int t = x.pointcrosscircle(y,c1.p,c2.p);
if(!t)return 0;
c1.r = c2.r = r;
return t;
}
//`得到与直线u相切,过点q,半径为r1的圆`
//`测试:UVA12304`
int getcircle(Line u,Point q,double r1,circle &c1,circle &c2){
double dis = u.dispointtoline(q);
if(sgn(dis-r1*2)>0)return 0;
if(sgn(dis) == 0){
c1.p = q + ((u.e-u.s).rotleft().trunc(r1));
c2.p = q + ((u.e-u.s).rotright().trunc(r1));
c1.r = c2.r = r1;
return 2;
}
Line u1 = Line((u.s + (u.e-u.s).rotleft().trunc(r1)),(u.e + (u.e-u.s).rotleft().trunc(r1)));
Line u2 = Line((u.s + (u.e-u.s).rotright().trunc(r1)),(u.e + (u.e-u.s).rotright().trunc(r1)));
circle cc = circle(q,r1);
Point p1,p2;
if(!cc.pointcrossline(u1,p1,p2))cc.pointcrossline(u2,p1,p2);
c1 = circle(p1,r1);
if(p1 == p2){
c2 = c1;
return 1;
}
c2 = circle(p2,r1);
return 2;
}
//`同时与直线u,v相切,半径为r1的圆`
//`测试:UVA12304`
int getcircle(Line u,Line v,double r1,circle &c1,circle &c2,circle &c3,circle &c4){
if(u.parallel(v))return 0;//两直线平行
Line u1 = Line(u.s + (u.e-u.s).rotleft().trunc(r1),u.e + (u.e-u.s).rotleft().trunc(r1));
Line u2 = Line(u.s + (u.e-u.s).rotright().trunc(r1),u.e + (u.e-u.s).rotright().trunc(r1));
Line v1 = Line(v.s + (v.e-v.s).rotleft().trunc(r1),v.e + (v.e-v.s).rotleft().trunc(r1));
Line v2 = Line(v.s + (v.e-v.s).rotright().trunc(r1),v.e + (v.e-v.s).rotright().trunc(r1));
c1.r = c2.r = c3.r = c4.r = r1;
c1.p = u1.crosspoint(v1);
c2.p = u1.crosspoint(v2);
c3.p = u2.crosspoint(v1);
c4.p = u2.crosspoint(v2);
return 4;
}
//`同时与不相交圆cx,cy相切,半径为r1的圆`
//`测试:UVA12304`
int getcircle(circle cx,circle cy,double r1,circle &c1,circle &c2){
circle x(cx.p,r1+cx.r),y(cy.p,r1+cy.r);
int t = x.pointcrosscircle(y,c1.p,c2.p);
if(!t)return 0;
c1.r = c2.r = r1;
return t;
}
//`过一点作圆的切线(先判断点和圆的关系)`
//连接圆心到点以及两切点,利用相似三角形求出圆心到两切点连线距离
//`测试:UVA12304`
int tangentline(Point q,Line &u,Line &v){
int x = relation(q);
if(x == 2)//点在圆内
return 0;
if(x == 1)//点在圆上
{
u = Line(q,q + (q-p).rotleft());
v = u;
return 1;
}
//点在圆外
double d = p.distance(q);//点到圆心的距离
double l = r*r/d;//利用相似三角形得出圆心到两切点连线距离
double h = sqrt(r*r-l*l);//勾股定理求两切点距离一半
u = Line(q,p + ((q-p).trunc(l) + (q-p).rotleft().trunc(h)));
v = Line(q,p + ((q-p).trunc(l) + (q-p).rotright().trunc(h)));
return 2;
}
//`求两圆相交的面积`
double areacircle(circle v){
int rel = relationcircle(v);
if(rel >= 4)return 0.0;//外切或相离
if(rel <= 2)return min(area(),v.area());//内切或内含
//只处理相交的情况
//两交点连线把相交面积分成两部分,每一部分都可以用扇形减三角形得到
double d = p.distance(v.p);//圆心距
double hf = (r+v.r+d)/2.0;//海伦公式求三角形面积
double ss = 2*sqrt(hf*(hf-r)*(hf-v.r)*(hf-d));//多余的两个三角形面积
double a1 = acos((r*r+d*d-v.r*v.r)/(2.0*r*d));
a1 = a1*r*r;//扇形面积1
double a2 = acos((v.r*v.r+d*d-r*r)/(2.0*v.r*d));
a2 = a2*v.r*v.r;//扇形面积2
return a1+a2-ss;
}
//`求圆和三角形pab的相交面积`
//`测试:POJ3675 HDU3982 HDU2892`
double areatriangle(Point a,Point b){
if(sgn((p-a)^(p-b)) == 0)return 0.0;//三角形pab面积为0时
Point q[5];
int len = 0;
q[len++] = a;
Line l(a,b);
Point p1,p2;
if(pointcrossline(l,q[1],q[2])==2){
if(sgn((a-q[1])*(b-q[1]))<0)q[len++] = q[1];
if(sgn((a-q[2])*(b-q[2]))<0)q[len++] = q[2];
}
q[len++] = b;
if(len == 4 && sgn((q[0]-q[1])*(q[2]-q[1]))>0)swap(q[1],q[2]);
double res = 0;
for(int i = 0;i < len-1;i++){
if(relation(q[i])==0||relation(q[i+1])==0){
double arg = p.rad(q[i],q[i+1]);
res += r*r*arg/2.0;
}
else{
res += fabs((q[i]-p)^(q[i+1]-p))/2.0;
}
}
return res;
}
//两圆公切线
//返回值为公切线条数,-1表示无数条公切线
//线段起点都是大圆上切点,终点都是小圆v上切点(除了存在公切点时)
//测试:Aizu - CGL_7_G
int tangentcircle(circle v, Line l[4])
{
int t = relationcircle(v);
if(r < v.r)//使当前圆为半径大者
return v.tangentcircle(*this, l);
if(t == 5)//相离
{
//特判半径相同情况
if(sgn(r-v.r) == 0)
{
l[0].s = p+(v.p-p).rotleft().trunc(r);
l[0].e = l[0].s+v.p-p;
l[1].s = p+(v.p-p).rotright().trunc(r);
l[1].e = l[1].s+v.p-p;
Point q = (p+v.p)/2;
Line t1, t2;
tangentline(q, t1, t2);
l[2].s = t1.e;
l[3].s = t2.e;
v.tangentline(q, t1, t2);
l[2].e = t1.e;
l[3].e = t2.e;
}
else
{
double d = p.distance(v.p);
Point q = v.p+(v.p-p).trunc(v.r*d/(r-v.r));
Line t1, t2;
tangentline(q, t1, t2);
l[0].s = t1.e;
l[1].s = t2.e;
v.tangentline(q, t1, t2);
l[0].e = t1.e;
l[1].e = t2.e;
q = p+(v.p-p).trunc(r*d/(r+v.r));
tangentline(q, t1, t2);
l[2].s = t1.e;
l[3].s = t2.e;
v.tangentline(q, t1, t2);
l[2].e = t1.e;
l[3].e = t2.e;
}
}
else if(t == 4)//外切
{
//特判半径相同情况
if(sgn(r-v.r) == 0)
{
l[0].s = p+(v.p-p).rotleft().trunc(r);
l[0].e = l[0].s+v.p-p;
l[1].s = p+(v.p-p).rotright().trunc(r);
l[1].e = l[1].s+v.p-p;
Point q = (p+v.p)/2;
Line t1, t2;
tangentline(q, t1, t2);
l[2] = t1;
}
else
{
double d = p.distance(v.p);
Point q = v.p+(v.p-p).trunc(v.r*d/(r-v.r));
Line t1, t2;
tangentline(q, t1, t2);
l[0].s = t1.e;
l[1].s = t2.e;
v.tangentline(q, t1, t2);
l[0].e = t1.e;
l[1].e = t2.e;
q = p+(v.p-p).trunc(r);
tangentline(q, t1, t2);
l[2] = t1;
}
}
else if(t == 3)//相交
{
//特判半径相同情况
if(sgn(r-v.r) == 0)
{
l[0].s = p+(v.p-p).rotleft().trunc(r);
l[0].e = l[0].s+v.p-p;
l[1].s = p+(v.p-p).rotright().trunc(r);
l[1].e = l[1].s+v.p-p;
}
else
{
double d = p.distance(v.p);
Point q = v.p+(v.p-p).trunc(v.r*d/(r-v.r));
Line t1, t2;
tangentline(q, t1, t2);
l[0].s = t1.e;
l[1].s = t2.e;
v.tangentline(q, t1, t2);
l[0].e = t1.e;
l[1].e = t2.e;
}
}
else if(t == 2)//内切
{
//特判半径相同情况
if(sgn(r-v.r) == 0)
return -1;//无数条公切线
else
{
Point q = p+(v.p-p).trunc(r);
Line t1, t2;
tangentline(q, t1, t2);
l[0] = t1;
}
}
//内含的情况直接返回0
return t-1;
}
};
十一、例题
[两圆关系]Intersection Aizu CGL_7_A
[精度][三角形内切圆]Incircle of a Triangle Aizu CGL_7_B
[三角形外接圆]Circumscribed Circle of a Triangle Aizu CGL_7_C
[直线和圆交点]Cross Points of a Circle and a Line Aizu CGL_7_D
[两圆交点]Cross Points of Circles Aizu CGL_7_E
[过定点圆的切线]Tangent to a Circle Aizu CGL_7_F
[两圆公切线]Common Tangent Aizu CGL_7_G
[圆和多边形面积交]Intersection of a Circle and a Polygon Aizu CGL_7_H
[精度][两圆相交面积]Area of Intersection between Two Circles Aizu CGL_7_I