hdu 4773 圆的反演

Posted very_czy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了hdu 4773 圆的反演相关的知识,希望对你有一定的参考价值。

第一次接触反演算法。

通过反演圆以求得反演后的直线。

圆的相切即为直线和圆的相切,切点是关键。

值得注意的是,不过中心的直线反演后得到不过中心的圆,圆圆反演后得到另一个圆,因为中途可以得到一条直线,所以可以简化计算。

反演半径不可以随意选取,过大会导致精度问题。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

#define MEM(a,b) memset(a,b,sizeof(a))
#define REP(i,n) for(int i=1;i<=(n);++i)
#define REV(i,n) for(int i=(n);i>=1;--i)
#define FOR(i,a,b) for(int i=(a);i<=(b);++i)
#define RFOR(i,a,b) for(int i=(a);i>=(b);--i)
#define getmid(l,r) ((l) + ((r) - (l)) / 2)
#define MP(a,b) make_pair(a,b)

typedef long long ll;
typedef pair<int,int> pii;
const int INF = (1 << 30) - 1;
const double eps = 1e-10;

double add(double a,double b) //有误差的浮点加法
{
if(abs(a + b) < eps * (abs(a) + abs(b))) return 0;
return a + b;
}

struct Point
{
double x,y;
Point(double tx = 0,double ty = 0) : x(tx),y(ty) {}
Point operator + (Point p)
{
return Point(add(x,p.x),add(y,p.y));
}
Point operator - (Point p)
{
return Point(add(x,-p.x),add(y,-p.y));
}
Point operator * (double d)
{
return Point(x * d,y * d);
}
Point operator / (double d)
{
return Point(x / d,y / d);
}
Point Move(double a,double d)//从x正方向开始,逆时针
{
return Point(x + d * cos(a),y + d * sin(a));
}
void Read()
{
scanf("%lf%lf",&x,&y);
}
};

struct Circle
{
Point o;
double r;
Circle(double tx = 0,double ty = 0,double tr = 0) : o(tx,ty),r(tr) {}
void Read()
{
o.Read();
scanf("%lf",&r);
}
void Out()
{
printf("%.8f %.8f %.8f\n",o.x,o.y,r);
}
};

int Sign(double x) //判断x的正负
{
return (x > eps) - (x < -eps);
}

double Cross(Point a,Point b,Point c) //a为顶点的叉积
{
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}

double Dis(Point a,Point b)//距离
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

Circle c[5];
Point P;
int T,tot;
double R;

Circle Inver(Circle c1) //圆反演圆过程
{
Circle res;
double oc1 = Dis(P,c1.o);
double k1 = 1.0 / (oc1 - c1.r);
double k2 = 1.0 / (oc1 + c1.r);
res.r = 0.5 * (k1 - k2) * R * R;
double oc2 = 0.5 * (k1 + k2) * R * R;
res.o = P + (c1.o - P) * oc2 / oc1;
return res;
}

void Mark(Point a,Point b) //记下答案圆,直线反演回圆
{
++tot;
double t = fabs(Cross(a,P,b) / Dis(a,b)); //求出P点到直线的距离
c[tot].r = R * R / (2.0 * t);
double d = Dis(a,c[1].o);
c[tot].o = P + (a - c[1].o) * (c[tot].r / d); //因为向量(a,c[1].o)与公切线垂直,所以可以利用其长度相似出P到c[tot]圆心的距离
}

void Solve()
{
REP(i,2) c[i] = Inver(c[i]); //将已知两圆反演
if(c[1].r < c[2].r) swap(c[1],c[2]); //c[1]圆的半径较大
Point tmp = c[2].o - c[1].o;//基础应该转角多少
double a1 = atan2(tmp.y,tmp.x); //atan2的经典应用,求出直线的倾斜角!
double a2 = acos((c[1].r - c[2].r) / Dis(c[1].o,c[2].o));//两圆心应该倾斜多少
Point P1 = c[1].o.Move(a1 + a2,c[1].r);//计算切点
Point P2 = c[2].o.Move(a1 + a2,c[2].r);//计算切点
if(Sign(Cross(P1,c[1].o,P2)) == Sign(Cross(P1,P,P2))) Mark(P1,P2); //保证P与c[1]圆心在公切线同侧
P1 = c[1].o.Move(a1 - a2,c[1].r);//同样要考虑公切线在下(上)方的情况,计算切点
P2 = c[2].o.Move(a1 - a2,c[2].r);//计算切点
if(Sign(Cross(P1,c[1].o,P2)) == Sign(Cross(P1,P,P2))) Mark(P1,P2); //保证P与c[1]圆心在公切线同侧
}

int main()
{
R = 10.0; //自定义的反演半径,不可过大
scanf("%d",&T);
REP(tt,T)
{
tot = 2;
REP(i,2) c[i].Read();
P.Read();
Solve();
printf("%d\n",tot - 2);
FOR(i,3,tot) c[i].Out();
}
return 0;
}

 

以上是关于hdu 4773 圆的反演的主要内容,如果未能解决你的问题,请参考以下文章

模板计几圆的反演

2017多校第6场 HDU 6097 Mindis 计算几何,圆的反演

计算几何圆反演hdu6158 The Designer

科技浅谈圆的反演

hdu1695 GCD(莫比乌斯反演)

HDU1695GCD(莫比乌斯反演)