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;
}

 

posted on 2016-03-16 15:38  very_czy  阅读(294)  评论(0编辑  收藏  举报

导航