Problem:
Milky Way
天の川
English text is not available in this practice contest.
天の川交通公社は星間旅行ツアーの企画・運営を行う旅行会社である。
天の川交通公社では「天の川横断織姫彦星体験ツアー」という企画を計画中である。
このツアーは琴座のベガを出発し、
星々を巡って鷲座のアルタイルに向かうというものである。
あなたは天の川交通公社に社員であり、
ツアーの経路の選択を任されている。
簡単のため天の川は2次元座標上にあるものとし、
星は五芒星で表現されるものとする。
ツアーに使用する宇宙船には特殊なエンジンが搭載されており、
五芒星の線分上はエネルギー無しで移動することができる。
一方、五芒星の間を移動する際には、その距離に比例したエネルギーが必要となる。
近年、天の川交通公社の売上は低迷しており、
宇宙船のエネルギー代を始めとした各種必要経費の節約を迫られている。
あなたの仕事は、ベガからアルタイルに移動する際、
星間移動距離の総和が最小になるような経路を求め、
その総和を出力するプログラムを書くことである。
ある五芒星からその内部に含まれる別の五芒星に移動する場合、
五芒星同士が接していない場合は星間の移動として扱われることに注意せよ。
図D-1は3つ目のSample Inputを図示したものである。
図中、赤色の線分は星間移動距離の総和が最小になるような経路の、
星間移動の部分を表している。
図 D-1: 星間の移動
Input
入力は1つ以上のデータセットからなる。1つのデータセットは次の形式をしている。
N
M
L
x
1
y
1
a
1
r
1
x
2
y
2
a
2
r
2
...
x
N
y
N
a
N
r
N
各テストケースの初めの1行は整数N、M、Lからなり、
Nは星の数(1 ≤ N ≤ 100)、
Mはベガの番号(1 ≤ M ≤ N)、
Lはアルタイルの番号(1 ≤ L ≤ N)を表す。
続くN行には各星の情報が与えられる。
各行はx
i
、y
i
、a
i
、r
i
の4つの整数からなり、
x
i
はi番目の星の中心のx座標(0 ≤ x
i
≤ 1,000)、
y
i
はi番目の星の中心のy座標(0 ≤ y
i
≤ 1,000)、
a
i
はi番目の星の中心座標と星の先端を結んだ直線がy軸となす角度(0 ≤ a
i
< 72)、
r
i
はi番目の星の中心座標から星の先端までの長さ(1 ≤ r
i
≤ 1,000)である。
入力の終わりは3つのゼロを含む行で表される。
図 D-2の左の星はx=5, y=10, a=0, r=5の星を表しており、右の星はx=15, y=10, a=30, r=5の星を表している。
図 D-2: 星の例
Output
各入力ごとに、ベガからアルタイルに移動する際に必要な星間移動距離の総和の最小値を1行に出力せよ。
出力には、0.000001を超える誤差があってはならない。
Sample Input
1 1 1
5 5 0 5
2 1 2
5 5 0 5
15 5 0 5
3 2 3
15 15 0 5
5 5 10 5
25 25 20 5
0 0 0
Output for Sample Input
0.00000000000000000000
0.48943483704846357796
9.79033725601359705593

Submitted solutions:

code_snippet: #pragma GCC target("avx2")
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")

// #ifndef ONLINE_JUDGE
// #define _GLIBCXX_DEBUG // 配列外参照のエラー
// #endif

// スタンダードライブラリ
#include <bits/stdc++.h>
using namespace std;

// 型関連
using ll = long long;
using lint = long long;
using pll = pair<ll, ll>;
using plll = tuple<ll, ll, ll>;
using pii = pair<int, int>;
using piii = tuple<ll, ll, ll>;
using vi = vector<int>;
using vvi = vector<vector<int>>;
using vvvi = vector<vector<vector<int>>>;
using vvvvi = vector<vector<vector<vector<int>>>>;
using vl = vector<ll>;
using vvl = vector<vector<ll>>;
using vvvl = vector<vector<vector<ll>>>;
using vvvvl = vector<vector<vector<vector<ll>>>>;

template <class T> using prique = priority_queue<T>;

// 型定数
constexpr ll mod = 998244353;
constexpr ll MOD = 1000000007;
int INF32 = 2e9;
ll INF64 = 9e18;
#define endl '\n';

int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};

/////////////////////////// print関連
template <typename T> void print(vector<T> A);
void print();
template <class Head, class... Tail> void print(Head &&head, Tail &&...tail);
void print(string S);
template <typename T> void print(vector<vector<T>> &F);

inline void Yes(bool f);

#define equals(a, b) (fabs((a) - (b)) < EPS)

const double EPS = 1e-10;
const double PI = asinl(1) * 2;

struct Point {
    double x, y;
    Point() {}
    Point(double x, double y) : x(x), y(y) {}
    Point operator+(Point p) { return Point(x + p.x, y + p.y); }
    Point operator-(Point p) { return Point(x - p.x, y - p.y); }
    Point operator*(double k) { return Point(x * k, y * k); }
    Point operator/(double k) { return Point(x / k, y / k); }
    double norm() { return x * x + y * y; }
    double abs() { return sqrt(norm()); }

    bool operator<(const Point &p) const {
        return x != p.x ? x < p.x : y < p.y;
        // grid-point only
        // return !equals(x,p.x)?x<p.x:!equals(y,p.y)?y<p.y:0;
    }

    bool operator==(const Point &p) const {
        return fabs(x - p.x) < EPS and fabs(y - p.y) < EPS;
    }
};

bool sort_x(Point a, Point b) { return a.x != b.x ? a.x < b.x : a.y < b.y; }

bool sort_y(Point a, Point b) { return a.y != b.y ? a.y < b.y : a.x < b.x; }

typedef Point Vector;
typedef vector<Point> Polygon;

struct Segment {
    Point p1, p2;
    Segment() {}
    Segment(Point p1, Point p2) : p1(p1), p2(p2) {}
};
typedef Segment Line;

struct Circle {
    Point c;
    double r;
    Circle() {}
    Circle(Point c, double r) : c(c), r(r) {}
};

double norm(Vector a) { return a.x * a.x + a.y * a.y; }

double abs(Vector a) { return sqrt(norm(a)); }

double dot(Vector a, Vector b) { return a.x * b.x + a.y * b.y; }

double cross(Vector a, Vector b) { return a.x * b.y - a.y * b.x; }

Point orth(Point p) { return Point(-p.y, p.x); }

bool isOrthogonal(Vector a, Vector b) { return equals(dot(a, b), 0.0); }

bool isOrthogonal(Point a1, Point a2, Point b1, Point b2) {
    return isOrthogonal(a1 - a2, b1 - b2);
}

bool isOrthogonal(Segment s1, Segment s2) {
    return equals(dot(s1.p2 - s1.p1, s2.p2 - s2.p1), 0.0);
}

bool isParallel(Vector a, Vector b) { return equals(cross(a, b), 0.0); }

bool isParallel(Point a1, Point a2, Point b1, Point b2) {
    return isParallel(a1 - a2, b1 - b2);
}

bool isParallel(Segment s1, Segment s2) {
    return equals(cross(s1.p2 - s1.p1, s2.p2 - s2.p1), 0.0);
}

Point project(Segment s, Point p) {
    Vector base = s.p2 - s.p1;
    double r = dot(p - s.p1, base) / norm(base);
    return s.p1 + base * r;
}

Point reflect(Segment s, Point p) { return p + (project(s, p) - p) * 2.0; }

double arg(Vector p) { return atan2(p.y, p.x); }

Vector polar(double a, double r) { return Point(cos(r) * a, sin(r) * a); }

// COUNTER CLOCKWISE
static const int CCW_COUNTER_CLOCKWISE = 1;
static const int CCW_CLOCKWISE = -1;
static const int CCW_ONLINE_BACK = 2;
static const int CCW_ONLINE_FRONT = -2;
static const int CCW_ON_SEGMENT = 0;

int ccw(Point p0, Point p1, Point p2) {
    Vector a = p1 - p0;
    Vector b = p2 - p0;
    if (cross(a, b) > EPS)
        return CCW_COUNTER_CLOCKWISE;
    if (cross(a, b) < -EPS)
        return CCW_CLOCKWISE;
    if (dot(a, b) < -EPS)
        return CCW_ONLINE_BACK;
    if (a.norm() < b.norm())
        return CCW_ONLINE_FRONT;
    return CCW_ON_SEGMENT;
}

bool intersectSS(Point p1, Point p2, Point p3, Point p4) {
    return (ccw(p1, p2, p3) * ccw(p1, p2, p4) <= 0 and
            ccw(p3, p4, p1) * ccw(p3, p4, p2) <= 0);
}

bool intersectSS(Segment s1, Segment s2) {
    return intersectSS(s1.p1, s1.p2, s2.p1, s2.p2);
}

bool intersectPS(Polygon p, Segment l) {
    int n = p.size();
    for (int i = 0; i < n; ++i)
        if (intersectSS(Segment(p[i], p[(i + 1) % n]), l))
            return 1;
    return 0;
}

double getDistanceLP(Line l, Point p) {
    return fabs(cross(l.p2 - l.p1, p - l.p1) / abs(l.p2 - l.p1));
}

double getDistanceSP(Segment s, Point p) {
    if (dot(s.p2 - s.p1, p - s.p1) < 0.0)
        return abs(p - s.p1);
    if (dot(s.p1 - s.p2, p - s.p2) < 0.0)
        return abs(p - s.p2);
    return getDistanceLP(s, p);
}

double getDistanceSS(Segment s1, Segment s2) {
    if (intersectSS(s1, s2))
        return 0.0;
    return min(min(getDistanceSP(s1, s2.p1), getDistanceSP(s1, s2.p2)),
               min(getDistanceSP(s2, s1.p1), getDistanceSP(s2, s1.p2)));
}

// intercsect of circles
static const int ICC_SEPERATE = 4;
static const int ICC_CIRCUMSCRIBE = 3;
static const int ICC_INTERSECT = 2;
static const int ICC_INSCRIBE = 1;
static const int ICC_CONTAIN = 0;

int intersectCC(Circle c1, Circle c2) {
    if (c1.r < c2.r)
        swap(c1, c2);
    double d = abs(c1.c - c2.c);
    double r = c1.r + c2.r;
    if (equals(d, r))
        return ICC_CIRCUMSCRIBE;
    if (d > r)
        return ICC_SEPERATE;
    if (equals(d + c2.r, c1.r))
        return ICC_INSCRIBE;
    if (d + c2.r < c1.r)
        return ICC_CONTAIN;
    return ICC_INTERSECT;
}

bool intersectSC(Segment s, Circle c) { return getDistanceSP(s, c.c) <= c.r; }

int intersectCS(Circle c, Segment s) {
    if (norm(project(s, c.c) - c.c) - c.r * c.r > EPS)
        return 0;
    double d1 = abs(c.c - s.p1), d2 = abs(c.c - s.p2);
    if (d1 < c.r + EPS and d2 < c.r + EPS)
        return 0;
    if ((d1 < c.r - EPS and d2 > c.r + EPS) or
        (d1 > c.r + EPS and d2 < c.r - EPS))
        return 1;
    Point h = project(s, c.c);
    if (dot(s.p1 - h, s.p2 - h) < 0)
        return 2;
    return 0;
}

Point getCrossPointSS(Segment s1, Segment s2) {
    for (int k = 0; k < 2; ++k) {
        if (getDistanceSP(s1, s2.p1) < EPS)
            return s2.p1;
        if (getDistanceSP(s1, s2.p2) < EPS)
            return s2.p2;
        swap(s1, s2);
    }
    Vector base = s2.p2 - s2.p1;
    double d1 = fabs(cross(base, s1.p1 - s2.p1));
    double d2 = fabs(cross(base, s1.p2 - s2.p1));
    double t = d1 / (d1 + d2);
    return s1.p1 + (s1.p2 - s1.p1) * t;
}

Point getCrossPointLL(Line l1, Line l2) {
    double a = cross(l1.p2 - l1.p1, l2.p2 - l2.p1);
    double b = cross(l1.p2 - l1.p1, l1.p2 - l2.p1);
    if (fabs(a) < EPS and fabs(b) < EPS)
        return l2.p1;
    return l2.p1 + (l2.p2 - l2.p1) * (b / a);
}

Polygon getCrossPointCL(Circle c, Line l) {
    Polygon ps;
    Point pr = project(l, c.c);
    Vector e = (l.p2 - l.p1) / abs(l.p2 - l.p1);
    if (equals(getDistanceLP(l, c.c), c.r)) {
        ps.emplace_back(pr);
        return ps;
    }
    double base = sqrt(c.r * c.r - norm(pr - c.c));
    ps.emplace_back(pr + e * base);
    ps.emplace_back(pr - e * base);
    return ps;
}

Polygon getCrossPointCS(Circle c, Segment s) {
    Line l(s);
    Polygon res = getCrossPointCL(c, l);
    if (intersectCS(c, s) == 2)
        return res;
    if (res.size() > 1u) {
        if (dot(l.p1 - res[0], l.p2 - res[0]) > 0)
            swap(res[0], res[1]);
        res.pop_back();
    }
    return res;
}

Polygon getCrossPointCC(Circle c1, Circle c2) {
    Polygon p(2);
    double d = abs(c1.c - c2.c);
    double a = acos((c1.r * c1.r + d * d - c2.r * c2.r) / (2 * c1.r * d));
    double t = arg(c2.c - c1.c);
    p[0] = c1.c + polar(c1.r, t + a);
    p[1] = c1.c + polar(c1.r, t - a);
    return p;
}

// IN:2 ON:1 OUT:0
int contains(Polygon g, Point p) {
    int n = g.size();
    bool x = false;
    for (int i = 0; i < n; ++i) {
        Point a = g[i] - p, b = g[(i + 1) % n] - p;
        if (fabs(cross(a, b)) < EPS and dot(a, b) < EPS)
            return 1;
        if (a.y > b.y)
            swap(a, b);
        if (a.y < EPS and EPS < b.y and cross(a, b) > EPS)
            x = !x;
    }
    return (x ? 2 : 0);
}

// BEGIN IGNORE
Polygon andrewScan(Polygon s) {
    Polygon u, l;
    if (s.size() < 3)
        return s;
    sort(s.begin(), s.end());
    u.push_back(s[0]);
    u.push_back(s[1]);
    l.push_back(s[s.size() - 1]);
    l.push_back(s[s.size() - 2]);
    for (int i = 2; i < (int)s.size(); ++i) {
        for (int n = u.size();
             n >= 2 and ccw(u[n - 2], u[n - 1], s[i]) != CCW_CLOCKWISE; n--) {
            u.pop_back();
        }
        u.push_back(s[i]);
    }
    for (int i = s.size() - 3; i >= 0; i--) {
        for (int n = l.size();
             n >= 2 and ccw(l[n - 2], l[n - 1], s[i]) != CCW_CLOCKWISE; n--) {
            l.pop_back();
        }
        l.push_back(s[i]);
    }
    reverse(l.begin(), l.end());
    for (int i = u.size() - 2; i >= 1; i--)
        l.push_back(u[i]);
    return l;
}
// END IGNORE

Polygon convex_hull(Polygon ps) {
    int n = ps.size();
    sort(ps.begin(), ps.end(), sort_y);
    int k = 0;
    Polygon qs(n * 2);
    for (int i = 0; i < n; ++i) {
        while (k > 1 and cross(qs[k - 1] - qs[k - 2], ps[i] - qs[k - 1]) < 0)
            k--;
        qs[k++] = ps[i];
    }
    for (int i = n - 2, t = k; i >= 0; i--) {
        while (k > t and cross(qs[k - 1] - qs[k - 2], ps[i] - qs[k - 1]) < 0)
            k--;
        qs[k++] = ps[i];
    }
    qs.resize(k - 1);
    return qs;
}

double diameter(Polygon s) {
    Polygon p = s;
    int n = p.size();
    if (n == 2)
        return abs(p[0] - p[1]);
    int i = 0, j = 0;
    for (int k = 0; k < n; ++k) {
        if (p[i] < p[k])
            i = k;
        if (!(p[j] < p[k]))
            j = k;
    }
    double res = 0;
    int si = i, sj = j;
    while (i != sj or j != si) {
        res = max(res, abs(p[i] - p[j]));
        if (cross(p[(i + 1) % n] - p[i], p[(j + 1) % n] - p[j]) < 0.0) {
            i = (i + 1) % n;
        } else {
            j = (j + 1) % n;
        }
    }
    return res;
}

bool isConvex(Polygon p) {
    bool f = 1;
    int n = p.size();
    for (int i = 0; i < n; ++i) {
        int t = ccw(p[(i + n - 1) % n], p[i], p[(i + 1) % n]);
        f &= t != CCW_CLOCKWISE;
    }
    return f;
}

double area(Polygon s) {
    double res = 0;
    for (int i = 0; i < (int)s.size(); ++i) {
        res += cross(s[i], s[(i + 1) % s.size()]) / 2.0;
    }
    return res;
}

double area(Circle c1, Circle c2) {
    double d = abs(c1.c - c2.c);
    if (c1.r + c2.r <= d + EPS)
        return 0;
    if (d <= fabs(c1.r - c2.r)) {
        double r = min(c1.r, c2.r);
        return PI * r * r;
    }
    double res = 0;
    for (int k = 0; k < 2; ++k) {
        double rc = (d * d + c1.r * c1.r - c2.r * c2.r) / (2 * d * c1.r);
        double th = acosl(rc) * 2;
        res += (th - sinl(th)) * c1.r * c1.r / 2;
        swap(c1, c2);
    }
    return res;
}

Polygon convexCut(Polygon p, Line l) {
    Polygon q;
    for (int i = 0; i < (int)p.size(); ++i) {
        Point a = p[i], b = p[(i + 1) % p.size()];
        if (ccw(l.p1, l.p2, a) != -1)
            q.push_back(a);
        if (ccw(l.p1, l.p2, a) * ccw(l.p1, l.p2, b) < 0)
            q.push_back(getCrossPointLL(Line(a, b), l));
    }
    return q;
}

Line bisector(Point p1, Point p2) {
    Circle c1 = Circle(p1, abs(p1 - p2)), c2 = Circle(p2, abs(p1 - p2));
    Polygon p = getCrossPointCC(c1, c2);
    if (cross(p2 - p1, p[0] - p1) > 0)
        swap(p[0], p[1]);
    return Line(p[0], p[1]);
}

Vector translate(Vector v, double theta) {
    Vector res;
    res.x = cos(theta) * v.x - sin(theta) * v.y;
    res.y = sin(theta) * v.x + cos(theta) * v.y;
    return res;
}

vector<Line> corner(Line l1, Line l2) {
    vector<Line> res;
    if (isParallel(l1, l2)) {
        double d = getDistanceLP(l1, l2.p1) / 2.0;
        Vector v1 = l1.p2 - l1.p1;
        v1 = v1 / v1.abs() * d;
        Point p = l2.p1 + translate(v1, 90.0 * (PI / 180.0));
        double d1 = getDistanceLP(l1, p);
        double d2 = getDistanceLP(l2, p);
        if (fabs(d1 - d2) > d) {
            p = l2.p1 + translate(v1, -90.0 * (PI / 180.0));
        }
        res.push_back(Line(p, p + v1));
    } else {
        Point p = getCrossPointLL(l1, l2);
        Vector v1 = l1.p2 - l1.p1, v2 = l2.p2 - l2.p1;
        v1 = v1 / v1.abs();
        v2 = v2 / v2.abs();
        res.push_back(Line(p, p + (v1 + v2)));
        res.push_back(Line(p, p + translate(v1 + v2, 90.0 * (PI / 180.0))));
    }
    return res;
}

Polygon tangent(Circle c1, Point p2) {
    Circle c2 = Circle(p2, sqrt(norm(c1.c - p2) - c1.r * c1.r));
    Polygon p = getCrossPointCC(c1, c2);
    sort(p.begin(), p.end());
    return p;
}

vector<Line> tangent(Circle c1, Circle c2) {
    vector<Line> ls;
    if (c1.r < c2.r)
        swap(c1, c2);
    double g = norm(c1.c - c2.c);
    if (equals(g, 0))
        return ls;
    Point u = (c2.c - c1.c) / sqrt(g);
    Point v = orth(u);
    for (int s = 1; s >= -1; s -= 2) {
        double h = (c1.r + s * c2.r) / sqrt(g);
        if (equals(1 - h * h, 0)) {
            ls.emplace_back(c1.c + u * c1.r, c1.c + (u + v) * c1.r);
        } else if (1 - h * h > 0) {
            Point uu = u * h, vv = v * sqrt(1 - h * h);
            ls.emplace_back(c1.c + (uu + vv) * c1.r,
                            c2.c - (uu + vv) * c2.r * s);
            ls.emplace_back(c1.c + (uu - vv) * c1.r,
                            c2.c - (uu - vv) * c2.r * s);
        }
    }

    return ls;
}

double closest_pair(Polygon &a, int l = 0, int r = -1) {
    if (r < 0) {
        r = a.size();
        sort(a.begin(), a.end(), sort_x);
    }
    if (r - l <= 1)
        return abs(a[0] - a[1]);
    int m = (l + r) >> 1;
    double x = a[m].x;
    double d = min(closest_pair(a, l, m), closest_pair(a, m, r));
    inplace_merge(a.begin() + l, a.begin() + m, a.begin() + r, sort_y);

    Polygon b;
    for (int i = l; i < r; ++i) {
        if (fabs(a[i].x - x) >= d)
            continue;
        for (int j = 0; j < (int)b.size(); ++j) {
            double dy = a[i].y - next(b.rbegin(), j)->y;
            if (dy >= d)
                break;
            d = min(d, abs(a[i] - *next(b.rbegin(), j)));
        }
        b.emplace_back(a[i]);
    }
    return d;
}

vector<vector<int>> segmentArrangement(vector<Segment> &ss, Polygon &ps) {
    int n = ss.size();
    for (int i = 0; i < n; ++i) {
        ps.emplace_back(ss[i].p1);
        ps.emplace_back(ss[i].p2);
        for (int j = i + 1; j < n; ++j)
            if (intersectSS(ss[i], ss[j]))
                ps.emplace_back(getCrossPointSS(ss[i], ss[j]));
    }
    sort(ps.begin(), ps.end());
    ps.erase(unique(ps.begin(), ps.end()), ps.end());

    vector<vector<int>> G(ps.size());
    for (int i = 0; i < n; ++i) {
        vector<pair<double, int>> ls;
        for (int j = 0; j < (int)ps.size(); ++j)
            if (getDistanceSP(ss[i], ps[j]) < EPS)
                ls.emplace_back(make_pair(norm(ss[i].p1 - ps[j]), j));

        sort(ls.begin(), ls.end());
        for (int j = 0; j + 1 < (int)ls.size(); ++j) {
            int a = ls[j].second, b = ls[j + 1].second;
            G[a].emplace_back(b);
            G[b].emplace_back(a);
        }
    }
    for (auto &v : G) {
        sort(v.begin(), v.end());
        v.erase(unique(v.begin(), v.end()), v.end());
    }
    return G;
}

struct EndPoint {
    Point p;
    int seg, st;
    EndPoint() {}
    EndPoint(Point p, int seg, int st) : p(p), seg(seg), st(st) {}
    bool operator<(const EndPoint &ep) const {
        if (p.y == ep.p.y)
            return st < ep.st;
        return p.y < ep.p.y;
    }
};

int manhattan_intersection(vector<Segment> ss, const int INF) {
    const int BTM = 0;
    const int LFT = 1;
    const int RGH = 2;
    const int TOP = 3;

    int n = ss.size();
    vector<EndPoint> ep;
    for (int i = 0; i < n; ++i) {
        if (ss[i].p1.y == ss[i].p2.y) {
            if (ss[i].p1.x > ss[i].p2.x)
                swap(ss[i].p1, ss[i].p2);
            ep.emplace_back(ss[i].p1, i, LFT);
            ep.emplace_back(ss[i].p2, i, RGH);
        } else {
            if (ss[i].p1.y > ss[i].p2.y)
                swap(ss[i].p1, ss[i].p2);
            ep.emplace_back(ss[i].p1, i, BTM);
            ep.emplace_back(ss[i].p2, i, TOP);
        }
    }
    sort(ep.begin(), ep.end());

    set<int> bt;
    bt.insert(INF);

    int cnt = 0;
    for (int i = 0; i < n * 2; ++i) {
        if (ep[i].st == TOP) {
            bt.erase(ep[i].p.x);
        } else if (ep[i].st == BTM) {
            bt.emplace(ep[i].p.x);
        } else if (ep[i].st == LFT) {
            auto b = bt.lower_bound(ss[ep[i].seg].p1.x);
            auto e = bt.upper_bound(ss[ep[i].seg].p2.x);
            cnt += distance(b, e);
        }
    }

    return cnt;
}

double area(Polygon ps, Circle c) {
    if (ps.size() < 3u)
        return 0;
    function<double(Circle, Point, Point)> dfs = [&](Circle c, Point a,
                                                     Point b) {
        Vector va = c.c - a, vb = c.c - b;
        double f = cross(va, vb), res = 0;
        if (equals(f, 0.0))
            return res;
        if (max(abs(va), abs(vb)) < c.r + EPS)
            return f;
        Vector d(dot(va, vb), cross(va, vb));
        if (getDistanceSP(Segment(a, b), c.c) > c.r - EPS)
            return c.r * c.r * atan2(d.y, d.x);
        auto u = getCrossPointCS(c, Segment(a, b));
        if (u.empty())
            return res;
        if (u.size() > 1u and dot(u[1] - u[0], a - u[0]) > 0)
            swap(u[0], u[1]);
        u.emplace(u.begin(), a);
        u.emplace_back(b);
        for (int i = 1; i < (int)u.size(); ++i)
            res += dfs(c, u[i - 1], u[i]);
        return res;
    };
    double res = 0;
    for (int i = 0; i < (int)ps.size(); ++i)
        res += dfs(c, ps[i], ps[(i + 1) % ps.size()]);
    return res / 2;
}

double dis(Point a, Point b) {
    return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

///////////////////ここから//////////////////////
bool solve() {
    int N, M, L;
    cin >> N >> M >> L;
    if (N == 0) {
        return false;
    }
    M--;
    L--;
    vector<vector<Point>> vvP(N);
    for (int i = 0; i < N; i++) {
        double x, y, a, r;
        cin >> x >> y >> a >> r;
        for (int j = 0; j < 5; j++) {
            double theta = -a * M_PI / 180 - 2 * M_PI * j / 5;
            double xj = x + r * sin(theta), yj = y + r * cos(theta);
            vvP[i].emplace_back(xj, yj);
        }
    }
    vector<vector<double>> E(N, vector<double>(N, INF32));
    for (int i = 0; i < N; i++) {
        for (int j = i; j < N; j++) {
            for (int k = 0; k < 5; k++) {
                for (int l = 0; l < 5; l++) {
                    
                    
                    Segment S1(vvP[i][k], vvP[i][(k + 2) % 5]);
                    Segment S2(vvP[j][l], vvP[j][(l + 2) % 5]);
                    if (intersectSS(S1, S2)) {
                        E[i][j] = 0;
                        E[j][i] = 0;
                    }


                    double d = min(getDistanceSP(S1, vvP[j][l]),getDistanceSP(S2, vvP[i][k]));
                    
                    E[i][j] = min(E[i][j], d);
                    E[j][i] = min(E[j][i], d);
                }
            }
        }
    }
    for (int k = 0; k < N; k++) {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (E[i][j] > E[i][k] + E[k][j]) {
                    E[i][j] = E[i][k] + E[k][j];
                }
            }
        }
    }
    cout << fixed << setprecision(10);
    cout << E[M][L] << endl;

    return true;
}

int main() {
    ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    while (solve())
        ;
}

////////////////////print/////////////////////////
// 1次元ベクトルを出力する
template <typename T> void print(vector<T> A) {
    if (A.size() == 0) {
        return;
    }
    for (size_t i = 0; i < A.size() - 1; i++) {
        cout << A[i] << ' ';
    }
    cout << A[A.size() - 1] << endl;
    return;
}

// 2次元ベクトルを出力する
template <typename T> void print(vector<vector<T>> &F) {
    for (size_t i = 0; i < F.size(); i++) {
        for (size_t j = 0; j < F[i].size(); j++) {
            cout << F[i][j];
            if (j < F[i].size() - 1) {
                cout << ' ';
            }
        }
        cout << endl;
    }
}

// 空白行を出力するprint
void print() { cout << endl; }

// 数値・文字列を空白区切りで出力する. print(a,b)など
template <class Head, class... Tail> void print(Head &&head, Tail &&...tail) {
    cout << head;
    if (sizeof...(tail) != 0)
        cout << " ";
    print(forward<Tail>(tail)...);
}

void print(string S) { cout << S << endl; }

//////////////出力関連

inline void Yes(bool f) {
    if (f) {
        cout << "Yes" << endl;
    } else {
        cout << "No" << endl;
    }
}
accuracy: 1
time_ms: 30
memory_kb: 4224
final_rank: 2

code_snippet: #include <bits/stdc++.h>
using namespace std;

// 書きかけの幾何ライブラリ
// バグがあるかも

namespace geometry {

using Real = long double;
constexpr Real eps = 1e-12;
constexpr Real PI = acosl(-1);
constexpr Real INF_REAL = numeric_limits<Real>::max();

bool equal_eps(Real a, Real b) {
    return abs(a - b) < eps;
}

int sgn(Real a) {
    return equal_eps(a, 0) ? 0 : a > 0 ? 1 : -1;
}

struct Point {
    Real x, y;
    constexpr Point() : Point(0, 0) {}
    constexpr Point(Real _x, Real _y) : x(_x), y(_y) {}
    constexpr Point(const pair<Real, Real> &p) : x(p.first), y(p.second) {}
    Point &operator+=(const Point &p) {
        x += p.x;
        y += p.y;
        return *this;
    }
    Point &operator-=(const Point &p) {
        x -= p.x;
        y -= p.y;
        return *this;
    }
    Point &operator*=(const Real &r) {
        x *= r;
        y *= r;
        return *this;
    }
    Point &operator/=(const Real &r) {
        assert(!equal_eps(r, 0));
        x /= r;
        y /= r;
        return *this;
    }
    Point operator+(const Point &p) const { return Point(*this) += p; }
    Point operator-(const Point &p) const { return Point(*this) -= p; }
    Point operator*(const Real &r) const { return Point(*this) *= r; }
    Point operator/(const Real &r) const { return Point(*this) /= r; }
    Point operator-() const { return Point() - *this; }
    bool operator==(const Point &p) const {
        return equal_eps(x, p.x) && equal_eps(y, p.y);
    }
    bool operator!=(const Point &p) const {
        return !equal_eps(x, p.x) || !equal_eps(y, p.y);
    }
    Point rotate(Real rad) const {
        Real c = cosl(rad), s = sinl(rad);
        Real tx = x * c - y * s;
        Real ty = x * s + y * c;
        return Point(tx, ty);
    }
    Point unit() const {
        return (*this) / metric();
    }
    Real Re() const { return x; }
    Real Im() const { return y; }
    Real norm() const { return x * x + y * y; }
    Real metric() const { return sqrtl(norm()); }
    Real arg() const { return atan2l(y, x); }
    Real arg(Point q1, Point q2) const {
        q1 -= *this, q2 -= *this;
        return abs(q1.arg() - q2.arg());
    }
    friend Point operator*(Real r, const Point &p) {
        return p * r;
    }
    friend Point operator/(Real r, const Point &p) {
        return p / r;
    }
};

Real Re(const Point &p) { return p.Re(); }
Real Im(const Point &p) { return p.Im(); }
Real dot(const Point &p, const Point &q) {
    return p.x * q.x + p.y * q.y;
}
Real cross(const Point &p, const Point &q) {
    return p.x * q.y - p.y * q.x;
}
Real norm(const Point &p) {
    return p.norm();
}
Real metric(const Point &p) {
    return p.metric();
}
Real metric(const Point &p, const Point &q) {
    return metric(p - q);
}
Real arg(const Point &p) {
    return p.arg();
}

bool equal_point(const Point &p, const Point &q) {
    return equal_eps(p.x, q.x) && equal_eps(p.y, q.y);
}

constexpr Point INF_POINT = Point(INF_REAL, INF_REAL);

// a を基準にした b と c の位置関係
int ccw(const Point &a, const Point &b, const Point &c) {
    Point p = b - a, q = c - a;
    if (cross(p, q) > eps) return 1; // a, b, c の順で反時計回り
    if (cross(p, q) < -eps) return -1; // a, b, c の順で時計回り
    if (min(norm(p), norm(q)) < eps * eps) return 0; // a = c or b = c
    if (dot(p, q) < eps) return 2; // c, a, b の順で一直線
    if (norm(p) < norm(q)) return -2; // a, b, c の順で一直線
    return 0; // a, c, b の順で一直線
}

istream &operator>>(istream &is, Point& p) {
    Real x, y;
    is >> x >> y;
    p = Point(x, y);
    return is;
}

} // namespace geometry

namespace geometry {

struct Line {
    // Line : ax + by = c
    Real a, b, c;
    constexpr Line() = default;
    constexpr Line(Real _a, Real _b, Real _c) : a(_a), b(_b), c(_c) {}
    constexpr Line(const Point &p, const Point &q) : a(q.y - p.y), b(p.x - q.x), c(p.x * q.y - p.y * q.x) {}
    constexpr Line(Real _a, const Point &p) : a(_a), b(-1), c(_a * p.x - p.y) {}
    bool online(const Point &p) const {
        return equal_eps(a * p.x + b * p.y, c);
    }
    Real get_x(Real y) const {
        if (equal_eps(a, 0)) return INF_REAL;
        return (c - b * y) / a;
    }
    Real get_y(Real x) const {
        if (equal_eps(b, 0)) return INF_REAL;
        return (c - a * x) / b;
    }
    Line perpendicular(const Point &p) const {
        if (equal_eps(a, 0)) return Line(1, 0, p.x);
        if (equal_eps(0, b)) return Line(0, 1, p.y);
        return Line(b / a, p);
    }
    Point crossppoint(const Line &l) const {
        if (equal_eps(a * l.b, l.a * b)) return INF_POINT;
        Real d = a * l.b - l.a * b;
        return Point(l.b * c - b * l.c, l.c * a - c * l.a) / d;
    }
    Point projection(const Point &p) const {
        return crossppoint(perpendicular(p));
    }
    Point reflection(const Point &p) const {
        Point q = projection(p);
        return 2 * q - p;
    }
    Point unit() const {
        if (equal_eps(b, 0)) {
            return Point(0, 1);
        }
        Point p(0, get_y(0)), q(1, get_y(1));
        return (p - q) / metric(p - q);
    }
    bool is_orthogonal(const Line &l) const {
        return equal_eps(a * l.a + b * l.b, 0);
    }
    bool is_parallel(const Line &l) const {
        return equal_eps(a * l.b, l.a * b);
    }
    Real dist(const Point &p) const {
        Point q = projection(p);
        return metric(p - q);
    }
};

bool is_orthogonal(const Line &l1, const Line &l2) {
    return l1.is_orthogonal(l2);
}
bool is_parallel(const Line &l1, const Line &l2) {
    return l1.is_parallel(l2);
}
Point crosspoint(const Line &l1, const Line &l2) {
    return l1.crossppoint(l2);
}

} // namespace geometry

namespace geometry {

struct Segment {
    Line l;
    Point p, q;
    Segment() = default;
    Segment(const Point &_p, const Point &_q) : l(_p, _q), p(_p), q(_q) {}
    bool cross(const Segment &s) const {
        bool f1 = ccw(p, q, s.p) * ccw(p, q, s.q) <= 0;
        bool f2 = ccw(s.p, s.q, p) * ccw(s.p, s.q, q) <= 0;
        return f1 && f2;
    }
    Point crosspoint(const Segment &s) const {
        if (cross(s)) return l.crossppoint(s.l);
        return INF_POINT;
    }
    Real dist(const Point &r) const {
        if (dot(q - p, r - p) < eps) return (r - p).metric();
        if (dot(p - q, r - q) < eps) return (r - q).metric();
        return l.dist(r);
    }
    Real dist(const Segment &s) const {
        if (cross(s)) return 0;
        Real res = INF_REAL;
        res = min(res, dist(s.p));
        res = min(res, dist(s.q));
        res = min(res, s.dist(p));
        res = min(res, s.dist(q));
        return res;
    }
};

bool cross(const Segment &s, const Segment &t) {
    return s.cross(t);
}

Point crosspoint(const Segment &s, const Segment &t) {
    return s.crosspoint(t);
}

Real dist(const Segment &s, const Segment &t) {
    return s.dist(t);
}

} // nemespace geometry
using namespace geometry;

Real theta(Real t) {
    return t * PI / 180;
}

bool solve() {
    int N, M, L;
    cin >> N >> M >> L;
    if (N == 0) return false;
    M--, L--;
    vector<array<Segment, 5>> stars(N);
    for (int i = 0; i < N; i++) {
        Point p;
        Real a, r;
        cin >> p >> a >> r;
        a += 90;
        auto &S = stars[i];
        for (int j = 0; j < 5; j++) {
            int k = j + 2;
            S[j] = Segment(
                p + r * Point(cosl(theta(a + j * 72)), sinl(theta(a + j * 72))),
                p + r * Point(cosl(theta(a + k * 72)), sinl(theta(a + k * 72)))
            );
        }
    }
    vector<vector<Real>> D(N, vector<Real>(N, INF_REAL / 3));
    for (int i = 0; i < N; i++) {
        for (int j = i; j < N; j++) {
            for (auto s : stars[i]) {
                for (auto t : stars[j]) {
                    D[i][j] = D[j][i] = min(D[i][j], s.dist(t));
                }
            }
        }
    }
    for (int k = 0; k < N; k++) {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                D[i][j] = min(D[i][j], D[i][k] + D[k][j]);
            }
        }
    }
    cout << fixed << setprecision(16) << D[M][L] << '\n';
    return true;
}

int main() {
    while (solve()) {}
}
accuracy: 1
time_ms: 160
memory_kb: 4480
final_rank: 5

code_snippet: #include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
#define rep(i, n) for (ll i = 0; i < (ll)(n); i++)
#define rrep(i,start,end) for (ll i = start;i >= (ll)(end);i--)
#define repn(i,end) for(ll i = 0; i <= (ll)(end); i++)
#define reps(i,start,end) for(ll i = start; i < (ll)(end); i++)
#define repsn(i,start,end) for(ll i = start; i <= (ll)(end); i++)
#define each(p,a) for(auto &p:a)
typedef vector<ll> vll;
typedef vector<pair<ll ,ll>> vpll;
typedef vector<vector<ll>> vvll;
typedef set<ll> sll;
typedef map<ll , ll> mpll;
typedef pair<ll ,ll> pll;
typedef tuple<ll , ll , ll> tpl3;
#define LL(...) ll __VA_ARGS__; input(__VA_ARGS__)
#define LD(...) ld __VA_ARGS__; input(__VA_ARGS__)
#define Str(...) string __VA_ARGS__; input(__VA_ARGS__)
#define Ch(...) char __VA_ARGS__; input(__VA_ARGS__)
#define all(a)  (a).begin(),(a).end()
#define UNIQUE(v) v.erase( unique(v.begin(), v.end()), v.end() );
#define sz(x) (ll)x.size()
// << std::fixed << std::setprecision(10)
const ll INF = 1LL << 60;
const ld EPS = 1e-9;
 
ll lceil(ll a,ll b){if(a%b==0){return a/b;}if(a>=0){return (a/b)+1;}else{return -((-a)/b);}}
ll lfloor(ll a,ll b){if(a%b==0){return a/b;}if(a>=0){return (a/b);}else{return -((-a)/b)-1;}}
inline ll positive_mod(ll a,ll m){return (a % m + m)%m;}
inline ll popcnt(ull a){ return __builtin_popcountll(a);}
//0indexed
inline ll topbit(ll a){assert(a != 0);return 63 - __builtin_clzll(a);}
inline ll smlbit(ll a){assert(a != 0);return __builtin_ctzll(a);}
template<class T> bool chmin(T& a, T b){if(a > b){a = b;return true;}return false;}
template<class T> bool chmax(T& a, T b){if(a < b){a = b;return true;}return false;}
template<typename T> std::istream &operator>>(std::istream&is,std::vector<T>&v){for(T &in:v){is>>in;}return is;}
template<typename T> std::ostream &operator<<(std::ostream&os,const std::vector<T>&v){for(auto it=std::begin(v);it!=std::end(v);){os<<*it<<((++it)!=std::end(v)?" ":"");}return os;}
template<typename T1, typename T2>std::ostream &operator<< (std::ostream &os, std::pair<T1,T2> p){os << "{" << p.first << "," << p.second << "}";return os;}
template<class... T>void input(T&... a){(cin >> ... >> a);}
void print(){cout << endl;}
template<class T, class... Ts>void print(const T& a, const Ts&... b){cout << a;((cout << ' ' << b), ...);cout << endl;}
template<class T> void pspace(const T& a){ cout << a << ' ';}
void perr(){cerr << endl;}
template<class T, class... Ts>void perr(const T& a, const Ts&... b){cerr << a;((cerr << ' ' << b), ...);cerr << endl;}
void yes(bool i = true){ return print(i?"yes":"no"); }
void Yes(bool i = true){ return print(i?"Yes":"No"); }
void YES(bool i = true){ return print(i?"YES":"NO"); }
template <class T> vector<T> &operator++(vector<T> &v) {for(auto &e : v) e++;return v;}
template <class T> vector<T> operator++(vector<T> &v, signed) {auto res = v;for(auto &e : v) e++;return res;}
template <class T> vector<T> &operator--(vector<T> &v) {for(auto &e : v) e--;return v;}
template <class T> vector<T> operator--(vector<T> &v, signed) {auto res = v;for(auto &e : v) e--;return res;}
//grid探索用
vector<ll> _ta = {0,0,1,-1,1,1,-1,-1};
vector<ll> _yo = {1,-1,0,0,1,-1,1,-1};
bool isin(ll now_i,ll now_j,ll h,ll w){return (0<=now_i && now_i < h && 0 <= now_j && now_j < w);}
  
ll lpow(ll x,ll n){ll ans = 1;while(n >0){if(n & 1)ans *= x;x *= x;n >>= 1;}return ans;}
ll Modlpow(ll x,ll n,ll m){ll ans = 1;ll a = x%m;while(n >0){if(n & 1){ans *= a;ans%= m;}a *= a;a %= m;n >>= 1;}return ans;} 
const ll MOD9 = 998244353LL;
const ll MOD10 = 1000000007LL;



//参考 https://github.com/saphmitchy/deliair-lib
// 型名
// R:Real, P:Point, L:Line, S:Segment, C:Circle, VP:vector<Point>

#define X(p) real(p)
#define Y(p) imag(p)

using R = ld;
using P = complex<R>;
using VP = vector<P>;

//const R EPS = 1e-9; // ここは適宜調節する,いつものやつから消す
const R pi = acos(-1.0);

int sgn(R a) {
    return (a < -EPS) ? -1 : (a > EPS) ? 1 : 0;
} // 符号関数

bool eq(R a, R b) {//実数の一致判定
    return sgn(b - a) == 0;
}

P operator*(P p, R d) {//ベクトルのd倍
    return P(X(p) * d, Y(p) * d);
}

P operator/(P p, R d) {//ベクトルの1/d倍
    return p * (1 / d);
}

istream &operator>>(istream &is, P &p) {
    // R a, b; // 入力が小数
    int a, b; // 入力が整数
    is >> a >> b;
    p = P(a, b);
    return is;
}

ostream &operator<<(ostream &os, P p) {
    return os << X(p) << ' ' << Y(p);
}

R getarg(P b,P a){//ベクトルbはベクトルaを何radian回転させる必要があるか
  assert(sgn(abs(a)) != 0);//長さが0はだめ
  return arg(b/a);
}

bool cp_x(P p, P q) {//ベクトルの比較x軸で比較->y軸で比較
    if (!eq(X(p), X(q)))
        return X(p) < X(q);
    return Y(p) < Y(q);
}

bool cp_y(P p, P q) {//ベクトルの比較y軸で比較->x軸で比較
    if (!eq(Y(p), Y(q)))
        return Y(p) < Y(q);
    return X(p) < X(q);
}

struct L {//直線ab
    P a, b;
    L() {}
    L(P a, P b) : a(a), b(b) {}

    // 入出力（必要なら）
    friend ostream &operator<<(ostream &os, L &l) {
        return os << l.a << ' ' << l.b;
    }
    friend istream &operator>>(istream &is, L &l) {
        return is >> l.a >> l.b;
    }
};

struct S : L {//線分ab
    S() {}
    S(P a, P b) : L(a, b) {}
};

struct C {//中心p 半径rの円
    P p;
    R r;
    C() {}
    C(P p, R r) : p(p), r(r) {}
};

P rot(P p, R t) {//ベクトルの回転
    return p * P(cos(t), sin(t));
}

//2つのベクトルの内積
R dot(P p, P q) {
    return X(p) * X(q) + Y(p) * Y(q);
}

//2つのベクトルの外積
R det(P p, P q) {
    return X(p) * Y(q) - Y(p) * X(q);
}

// https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_1_C&lang=jp
int ccw(P a, P b, P c) { // 線分 ab に対する c の位置関係 
    b -= a, c -= a;//ベクトルab,ベクトルacにした
    if (sgn(det(b, c)) == 1)//外積右ねじ正
        return +1; // COUNTER_CLOCKWISE a,b,cが反時計回り
    if (sgn(det(b, c)) == -1)
        return -1; // CLOCKWISE
    if (dot(b, c) < 0.0)
        return +2; // ONLINE_BACK
    if (norm(b) < norm(c))
        return -2; // ONLINE_FRONT
    return 0;      // ON_SEGMENT
}

bool para(L a, L b) { // 平行判定
    return eq(det(a.b - a.a, b.b - b.a), 0.0);
}

bool orth(L a, L b) { // 垂直判定
    return eq(dot(a.b - a.a, b.b - b.a), 0.0);
}

P proj(L l, P p) { // 垂線の足
    R t = dot(p - l.a, l.b - l.a) / norm(l.b - l.a);
    return l.a + (l.b - l.a) * t;
}

// これいる？
// P proj(S s, P p) {
//     R t = dot(p - s.a, s.b - s.a) / norm(s.b - s.a);
//     return s.a + (s.b - s.a) * t;
// }

P refl(L l, P p) { // 線対称の位置にある点
    return p + (proj(l, p) - p) * 2.0;
}

bool inter(L l, P p) { // 交点を持つか判定
    return abs(ccw(l.a, l.b, p)) != 1;
}

bool inter(S s, P p) {
    return ccw(s.a, s.b, p) == 0;
}

bool inter(L l, L m) {
    if (!eq(det(l.b - l.a, m.b - m.a), 0.0))
        return true;
    return eq(det(l.b - l.a, m.b - l.a), 0.0);
}

bool inter(L l, S s) {
    return sgn(det(l.b - l.a, s.a - l.a) * det(l.b - l.a, s.b - l.a)) <= 0;
}

bool inter(S s, L l) {
    return inter(l, s);
}

bool inter(S s, S t) {
    if (ccw(s.a, s.b, t.a) * ccw(s.a, s.b, t.b) > 0)
        return false;
    return ccw(t.a, t.b, s.a) * ccw(t.a, t.b, s.b) <= 0;
}

R dist(P p, P q) {
    return abs(q - p);
}

R dist(L l, P p) {
    return abs(p - P(proj(l, p)));
}

//R dist(S s, P p) {
//    P h = proj(s, p);
//    if (inter(s, h))
//        return abs(h - p);
//    return min(abs(s.a - p), abs(s.b - p));
//}

R dist(S s, P p) {
    P h = proj(s, p);
    if(dot(p-s.a,s.b-s.a) <= 0) return abs(s.a-p);
    if(dot(p-s.b,s.a-s.b) <= 0) return abs(s.b-p);
    return abs(p - h);
}

R dist(L l, L m) {
    return inter(l, m) ? 0.0 : dist(l, m.a);
}

R dist(S s, S t) {
    if (inter(s, t))
        return 0.0;
    return min({dist(s, t.a), dist(s, t.b), dist(t, s.a), dist(t, s.b)});
}

R dist(L l, S s) {
    if (inter(l, s))
        return 0.0;
    return min(dist(l, s.a), dist(l, s.b));
}

R dist(S s, L l) {
    return dist(l, s);
}

bool inter(C c, L l) {
    return sgn(c.r - dist(l, c.p)) >= 0;
}

bool inter(C c, P p) {
    return eq(abs(p - c.p), c.r);
}

// 共通接線の本数
// 交点なし:4
// 外接:3
// 2点で交わる:2
// 内接:1
// 一方がもう一方を内包:0
int inter(C c1, C c2) {
    if (c1.r < c2.r)
        swap(c1, c2);
    R d = abs(c1.p - c2.p);
    int a = sgn(d - c1.r - c2.r);
    if (a >= 0)
        return 3 + a;
    return 1 + sgn(d - c1.r + c2.r);
}

VP crosspoint(L l, L m) {
    VP ret;
    if (!inter(l, m))
        return ret;
    R A = det(l.b - l.a, m.b - m.a);
    R B = det(l.b - l.a, l.b - m.a);
    if (eq(A, 0.0) && eq(B, 0.0)) {
        ret.emplace_back(m.a);
    } else {
        ret.emplace_back(m.a + (m.b - m.a) * B / A);
    }
    return ret;
}

VP crosspoint(S s, S t) {
    return inter(s, t) ? crosspoint(L(s), L(t)) : VP();
}

VP crosspoint(C c, L l) {//円と直線の交点
    P h = proj(l, c.p);
    P e = (l.b - l.a) / abs(l.b - l.a);
    VP ret;
    if (!inter(c, l))
        return ret;
    if (eq(dist(l, c.p), c.r)) {
        ret.emplace_back(h);
    } else {
        R b = sqrt(c.r * c.r - norm(h - c.p));
        ret.push_back(h + e * b), ret.push_back(h - e * b);
    }
    return ret;
}

VP crosspoint(C c, S s) {//円と線分の交点
    P h = proj(s, c.p);
    P e = (s.b - s.a) / abs(s.b - s.a);
    VP ret;
    if (!inter(c, s))
        return ret;
    if (eq(dist(s, c.p), c.r)) {
        ret.emplace_back(h);
    } else {
        R b = sqrt(c.r * c.r - norm(h - c.p));
        if(ccw(s.a,s.b,h - e * b) == 0){//s.aに近い方から線分上なら追加する
          ret.push_back(h - e * b);
        }
        if(ccw(s.a,s.b,h + e * b)==0){
          ret.push_back(h + e * b);
        }
    }
    return ret;
}

VP crosspoint(C c1, C c2) {//2つの円の交わる点
    R d = abs(c1.p - c2.p);
    R a = acos((c1.r * c1.r + d * d - c2.r * c2.r) / (2 * c1.r * d));
    R t = atan2(Y(c2.p) - Y(c1.p), X(c2.p) - X(c1.p));
    VP ret;
    if (inter(c1, c2) % 4 == 0) // 交わらないとき
        return ret;
    if (eq(a, 0.0)) {
        ret.emplace_back(P(c1.p + rot(P(c1.r, 0.0), t)));
    } else {
        P p1 = c1.p + rot(P(c1.r, 0.0), t + a);
        P p2 = c1.p + rot(P(c1.r, 0.0), t - a);
        ret.emplace_back(p1), ret.emplace_back(p2);
    }
    return ret;
}

VP cut(VP p, L l, bool border = true) { // 直線が多角形に切り取られる区間
    int n = sz(p);
    p.emplace_back(p[0]), p.emplace_back(p[1]);
    VP ret;
    rep(i, n) {
        if (!eq(dist(l, p[i]), 0) && !eq(dist(l, p[i + 1]), 0)) {
            S s(p[i], p[i + 1]);
            if (eq(dist(l, s), 0)) {
                auto res = crosspoint(l, s);
                ret.emplace_back(res[0]);
            }
        }
        if (eq(dist(l, p[i + 1]), 0)) {
            if ((eq(dist(l, p[i]), 0) || eq(dist(l, p[i + 2]), 0)) && !border)
                continue;
            S s(p[i], p[i + 2]);
            if (eq(dist(l, s), 0))
                ret.emplace_back(p[i + 1]);
        }
    }
    return ret;
}

VP rectangle(S s, R r) { // sを軸とした幅rの長方形
    P d = (s.a - s.b) * P(0, 1);
    d *= r / sqrt(norm(d));
    return VP{s.a + d, s.a - d, s.b - d, s.b + d};
}

L vertical_bisector(P p, P q) { // 垂直二等分線
    L l;
    l.a = (p + q) * 0.5;
    l.b = l.a + rot(q - p, pi * 0.5);
    return l;
}

L angle_bisector(P a,P b,P c){//角abcの二等分線(角bの２等分線)
  L l;
  l.a = b;
  R ang = atan2(Y(c-b),X(c-b)) - atan2(Y(a-b),X(a-b));//なす角
  ang/=2.0;
  l.b = l.a + rot(a-b,ang);
  return l;
}

C Apollonius(P p, P q, R a, R b) { // アポロニウスの円
    P p1 = (p * b + q * a) / (a + b), p2 = (-p * b + q * a) / (a - b);
    C c;
    c.p = (p1 + p2) * 0.5;
    c.r = abs(p1 - p2) * 0.5;
    return c;
}

R area(VP p) { // 多角形の面積
    R ret = 0.0;
    int n = sz(p);
    rep(i, n) ret += det(p[i], p[(i + 1) % n]);
    return abs(ret * 0.5);
}

int in_polygon(VP p, P q) { // IN:2, ON:1, OUT:0
    int n = sz(p);
    int ret = 0;
    rep(i, n) {
        P a = p[i] - q, b = p[(i + 1) % n] - q;
        if (eq(det(a, b), 0.0) && sgn(dot(a, b)) <= 0)
            return 1;
        if (Y(a) > Y(b))
            swap(a, b);
        if (sgn(Y(a)) <= 0 && sgn(Y(b)) == 1 && sgn(det(a, b)) == 1)
            ret ^= 2;
    }
    return ret;
}

VP tangent(C c, P p) { // 点 p を通る円 c の接線と c の接点
    return crosspoint(c, C(p, sqrt(norm(p - c.p) - c.r * c.r)));
}

vector<L> tangent(C c1, C c2) { // 共通接線
    vector<L> ret;
    if (c1.r < c2.r)
        swap(c1, c2);
    R r = abs(c2.p - c1.p);
    if (eq(r, 0.0))
        return ret;
    P u = (c2.p - c1.p) / r;
    P v = rot(u, pi * 0.5);
    for (R s : {1.0, -1.0}) {
        R h = (c1.r + c2.r * s) / r;
        if (eq(abs(h), 1.0)) {
            ret.emplace_back(c1.p + u * c1.r, c1.p + (u + v) * c1.r);
        } else if (abs(h) < 1.0) {
            P uu = u * h, vv = v * sqrt(1.0 - h * h);
            ret.emplace_back(c1.p + (uu + vv) * c1.r, c2.p - (uu + vv) * c2.r * s);
            ret.emplace_back(c1.p + (uu - vv) * c1.r, c2.p - (uu - vv) * c2.r * s);
        }
    }
    return ret;
}

VP convex_hull(VP p) { // 凸包
    sort(all(p), cp_x);
    p.erase(unique(all(p)), end(p));
    int n = sz(p), k = 0;
    if (n == 1)
        return p;
    VP ch(2 * n);
    for (int i = 0; i < n; ch[k++] = p[i++]) {
        while (k >= 2 && sgn(det(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1])) <= 0)
            k--;
    }
    for (int i = n - 2, t = k + 1; i >= 0; ch[k++] = p[i--]) {
        while (k >= t && sgn(det(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1])) <= 0)
            k--;
    }
    ch.resize(k - 1);
    return ch;
}

R closest_pair(VP p) { // 最近点対の距離
    if (sz(p) <= 1)
        return 1e18;
    sort(all(p), cp_x);
    VP memo(sz(p));

    function<R(int, int)> rec = [&](int l, int r) {
        if (r - l <= 1)
            return R(1e18);
        int m = (l + r) >> 1;
        R x = X(p[m]);
        R ret = min(rec(l, m), rec(m, r));
        inplace_merge(p.begin() + l, p.begin() + m, p.begin() + r, cp_y);
        int cnt = 0;
        reps(i, l, r) {
            if (abs(X(p[i]) - x) >= ret)
                continue;
            rep(j, cnt) {
                P d = p[i] - memo[cnt - j - 1];
                if (Y(d) >= ret)
                    break;
                chmin(ret, abs(d));
            }
            memo[cnt++] = p[i];
        }
        return ret;
    };

    return rec(0, sz(p));
}

R farthest_pair(VP p) {//最遠点対の距離
  VP ps = convex_hull(p);
  ll n = ps.size();
  if(n == 2){//凸包が潰れてる
    return dist(ps[0],ps[1]);
  }
  ll i = 0,j = 0;
  rep(k,n){//x軸方向に最も遠い点対を求める
    if(cp_x(ps[k],ps[i]))i = k;
    if(cp_x(ps[j],ps[k]))j = k;
  }
  R ret = 0;
  ll si = i,sj = j;
  while(i != sj || j != si){//180度反転しきるまで
    ret = max(ret,dist(ps[i],ps[j]));
    if(det(ps[(i+1)%n] - ps[i],ps[(j+1)%n]-ps[j]) < 0){
      i = (i + 1) % n;
    }else{
      j = (j + 1) % n;
    }
  }
  return ret;
}

// 原点, 点 a, 点 b とで囲まれる領域の面積 (三角形 ver と扇型 ver)
R calc_element(P a, P b, R cr,bool triangle){
  if(triangle)return det(a,b)/2;
  else{
    P tmp = b * (P(X(a),-Y(a)));
    R ang = atan2(Y(tmp),X(tmp));
    return cr * cr * ang/2;
  }
}

// 円 C と、三角形 ((0, 0), ia, ib) との共通部分の面積
R common_area(C c, P ia,P ib){
  P a = ia - c.p , b = ib - c.p;
  if(eq(abs(a-b),0))return 0;
  bool isin_a = (sgn(c.r - abs(a))>= 0);
  bool isin_b = (sgn(c.r - abs(b))>= 0);
  if(isin_a && isin_b)return calc_element(a,b,c.r,true);//aもbも円の中

  C oc(P(0,0),c.r);
  S seg(a,b);
  VP cr = crosspoint(oc,seg);
  if(cr.empty())return calc_element(a,b,c.r,false);
  P s = cr[0],t = cr.back();
  return calc_element(s,t,c.r,true) + calc_element(a,s,c.r,isin_a) + calc_element(t,b,c.r,isin_b);

}


R common_area(C c, VP vp){// 円cと多角形の共通部分の面積
  R ret = 0;
  ll n = vp.size();
  rep(i,n){
    ret += common_area(c,vp[i],vp[(i+1)%n]);
  }
  return ret;
}

R common_area(C p, C q) {// 円と円の共通部分の面積
    R d = abs(p.p - q.p);
    if (d >= p.r + q.r - EPS) return 0;
    else if (d <= abs(p.r - q.r) + EPS) return min(p.r, q.r) * min(p.r, q.r) * pi;
    R pcos = (p.r*p.r + d*d - q.r*q.r) / (p.r*d*2);
    R pang = acosl(pcos);
    R parea = p.r*p.r*pang - p.r*p.r*sin(pang*2)/2;
    R qcos = (q.r*q.r + d*d - p.r*p.r) / (q.r*d*2);
    R qang = acosl(qcos);
    R qarea = q.r*q.r*qang - q.r*q.r*sin(qang*2)/2;
    return parea + qarea;
}

vector<VP> divisions(vector<L> lf, R lim = 1e9) {
    vector<L> ls;
    each(l, lf) {
        bool ok = true;
        each(m, ls) {
            if (para(l, m) & inter(l, m.a)) {
                ok = false;
                break;
            }
        }
        if (ok)
            ls.emplace_back(l);
    }
    VP lc{P(-lim, -lim), P(lim, -lim), P(lim, lim), P(-lim, lim)};
    rep(i, 4) ls.emplace_back(lc[i], lc[(i + 1) % 4]);
    int m = sz(ls);
    VP ps;
    vector<vector<int>> lp(m);
    rep(i, m) {
        reps(j, i + 1, m) {
            each(p, crosspoint(ls[i], ls[j])) {
                if (max(abs(X(p)), abs(Y(p))) < lim + EPS) {
                    lp[i].emplace_back(sz(ps)), lp[j].emplace_back(sz(ps));
                    ps.emplace_back(p);
                }
            }
        }
    }
    int n = sz(ps);
    vector<int> id(n, -1), to;
    vector<R> rg;
    vector<vector<pair<R, int>>> li(n);
    rep(i, m) {
        sort(all(lp[i]), [&ps](int a, int b) { return cp_x(ps[a], ps[b]); });
        vector<int> q;
        rep(j, sz(lp[i])) {
            int me = id[lp[i][j]], st = j;
            auto np = ps[lp[i][j]];
            while (j + 1 < sz(lp[i])) {
                if (abs(ps[lp[i][j + 1]] - np) < EPS) {
                    j++;
                    if (id[lp[i][j]] != -1)
                        me = id[lp[i][j]];
                } else
                    break;
            }
            if (me == -1)
                me = lp[i][st];
            reps(k, st, j + 1) id[lp[i][k]] = me;
            q.emplace_back(me);
        }
        rep(i, sz(q) - 1) {
            P d = ps[q[i + 1]] - ps[q[i]];
            R s = atan2(Y(d), X(d)), t = atan2(-Y(d), -X(d));
            int x = q[i], y = q[i + 1];
            li[x].emplace_back(s, sz(to));
            li[x].emplace_back(s + pi * 2, sz(to));
            to.emplace_back(y), rg.emplace_back(t);
            li[y].emplace_back(t, sz(to));
            li[y].emplace_back(t + pi * 2, sz(to));
            to.emplace_back(x), rg.emplace_back(s);
        }
    }
    rep(i, n) sort(all(li[i]));
    vector<bool> u(sz(to), false);
    vector<VP> ret;
    rep(i, n) {
        each(l, li[i]) {
            int ns = l.second;
            if (u[ns])
                continue;
            VP nv;
            int no = ns;
            bool ok = true;
            while (1) {
                if (sz(nv) > 1) {
                    P x = nv[sz(nv) - 2], y = nv[sz(nv) - 1], z = ps[to[no]];
                    int c = ccw(x, y, z);
                    if (c == 1)
                        ok = false;
                    if (c != -1)
                        nv.pop_back();
                }
                nv.emplace_back(ps[to[no]]);
                u[no] = true;
                no = upper_bound(all(li[to[no]]), pair(rg[no] + EPS, -1))->second;
                if (no == ns)
                    break;
            }
            if (ok)
                ret.emplace_back(nv);
        }
    }
    return ret;
}

//ref https://github.com/drken1215/algorithm/blob/master/Geometry/arg_sort.cpp
//verify https://atcoder.jp/contests/abc139/submissions/me
//点列を偏角ソート
void arg_sort(VP &v){
  //原点=0,(pi,2pi] = -1  (0pi,pi] = 1
  auto sign = [&](const P &p){
    if(sgn(X(p)) == 0 && sgn(Y(p)) == 0){
      return 0;
    }else if(sgn(Y(p)) == -1 || (sgn(Y(p)) == 0 && sgn(X(p)) == 1)){
      return -1;
    }else{
      return 1;
    }
  };
  auto cp = [&](const P &p,const P &q){
    if(sign(p) != sign(q)){
      return sign(p) < sign(q);
    }else{//外積>0で判定
      //同じ向きにあるときは未定義、それぞれ決める
      return X(p) * Y(q) - Y(p) * X(q) > 0;
    }
  };
  sort(v.begin(),v.end(),cp);
}


using tpl4 = tuple<ll,ll,ll,ll>;
void solve(ll n,ll m,ll l){
  vector<tpl4> xyar(n);
  rep(i,n){
    LL(x,y,a,r);
    xyar[i] ={x,y,a,r};
  }
  vector<vector<P>> nodes(n,vector<P>(5));
  rep(i,n){
    auto [x,y,a,r] = xyar[i];
    ld baserad = (ld)(a+90)/180 * M_PI;
    ld drad = (ld)72/180 * M_PI;
    rep(j,5){
      nodes[i][j] = P(x+ r *cos(baserad + drad * j),y + r * sin(baserad + drad * j));
    }
  }
  vector<vector<S>> segs(n,vector<S>(5));
  rep(i,n){
    rep(j,5){
      ll k = (j+2) % 5;
      segs[i][j] = S(nodes[i][j],nodes[i][k]);
    }
  }
  
  
  ld limit = 100000;
  vector<vector<ld>> distance(n,vector<ld>(n,limit));

  rep(i,n){
    rep(j,i){
      // 線分と線分の距離だけ見ればいい
      rep(a,5)rep(b,5){
        chmin(distance[i][j], dist(segs[j][b],segs[i][a]));
      }

      //逆
      distance[j][i] = distance[i][j];
    }
  }


  //warshal floyd

  int size = (int)distance.size();
  rep(k,size){
    rep(i,size){
      rep(j,size){
        if(i == j){
          chmin(distance[i][j],ld(0));
        }
        if(!eq(distance[i][k],limit)  && !eq(distance[k][j],limit)){
          chmin(distance[i][j],distance[i][k] + distance[k][j]);
        }
      }
    }
  }
  cout << std::fixed << std::setprecision(10) <<  distance[m-1][l-1] << endl;



}
 
int main(){
  ios::sync_with_stdio(false);cin.tie(nullptr);
  while(true){
    LL(n,m,l);
    if(n == 0 && m == 0 && l == 0)break;
    solve(n,m,l);
  } 
}
accuracy: 1
time_ms: 350
memory_kb: 4480
final_rank: 11

code_snippet: #include <bits/stdc++.h>
using namespace std;
template <class T>
bool chmin(T& a, T b) {
  return a > b ? a = b, true : false;
}
template <class T>
bool chmax(T& a, T b) {
  return a < b ? a = b, true : false;
}

using ll = long long;
using ull = unsigned long long;
using uint = unsigned int;
template <class T = ll>
struct Edge {
  int to;
  T weight;
  bool operator==(Edge e) {
    return this->to == e.to and this->weight == e.weight;
  }
  bool operator<(Edge e) {
    return this->to == e.to ? this->weight < e.weight : this->to < e.to;
  }
};
#ifdef _DEBUG
#define SHOW(n)                         \
  {                                     \
    const auto& _ret = n;               \
    cerr << #n << ": " << _ret << endl; \
  }
#define MSG(x) cerr << x << endl;
#else
#define SHOW(n)
#define MSG(x)
#endif

////AtCoder Library
// #include <atcoder/all>
// using namespace atcoder;
////using mint = modint998244353;
////using mint = modint1000000007;
////using mint1 = dynamic_modint<0>;
// using mint = modint;
////mint::set_mod();
// istream& operator>>(istream& is, mint& x) { ll r; is >> r; x = r; return is;
// } ostream& operator<<(ostream& os, mint& x) { os << x.val(); return os; }

// boost
// #include <boost/multiprecision/cpp_int.hpp>
// using namespace boost::multiprecision;
// using l3 = int128_t;

using Real = double;
using Point = complex<Real>;
using Polygon = vector<Point>;
const Real EPS = 1e-8, PI = acos(-1);
Point operator*(const Point& p, const Real& d) {
  return Point(p.real() * d, p.imag() * d);
}
Point operator/(const Point& p, const Real& d) {
  return Point(p.real() / d, p.imag() / d);
}

istream& operator>>(istream& is, Point& p) {
  Real a, b;
  is >> a >> b;
  p = Point(a, b);
  return is;
}
int sign(const Real& r) {
  if (r <= -EPS) return -1;
  if (r >= +EPS) return +1;
  return 0;
}
bool equals(const Real& a, const Real& b) { return sign(a - b) == 0; }
namespace std {
bool operator<(const Point& a, const Point& b) {
  if (equals(a.real(), b.real())) return a.imag() < b.imag();
  return a.real() < b.real();
}
}  // namespace std
// namespace std
Real dot(const Point& a, const Point& b) { return (conj(a) * b).real(); }
Real cross(const Point& a, const Point& b) { return (conj(a) * b).imag(); }
struct Line {
  Point a, b;
  Line() = default;
  Line(Point a, Point b) : a(a), b(b) {}
};
using Segment = Line;

// 直線 l に対する点 p の射影
Point projection(const Line& l, const Point& p) {
  return l.a + (l.a - l.b) * dot(p - l.a, l.a - l.b) / norm(l.a - l.b);
}
// 直線 l に対する点 p の反射 (l と反対側にある点)
Point reflection(const Line& l, const Point& p) {
  return p + (projection(l, p) - p) * 2.0;
}
// 直線 l に対する点 p の射影

// 点 a から見た点 b,c の位置関係
// +1: b,c が反時計回り
// -1: b,c が時計回り
// +2: c,a,b がこの順で同一直線上にある
// -2: a,b,c がこの順で同一直線上にある
// 0: a,c,b がこの順で同一直線上にある
int ccw(const Point& a, Point b, Point c) {
  b -= a, c -= a;
  if (sign(cross(b, c)) == +1) return +1;
  if (sign(cross(b, c)) == -1) return -1;
  if (sign(dot(b, c)) == -1) return +2;
  if (norm(b) < norm(c)) return -2;
  return 0;
}

// 直線 a,b が直交するか判定
bool is_orthogonal(const Line& a, const Line& b) {
  return equals(dot(a.b - a.a, b.b - b.a), 0);
}
// 直線 a,b が並行か判定
bool is_parallel(const Line& a, const Line& b) {
  return equals(cross(a.b - a.a, b.b - b.a), 0);
}
// 線分 s,t が交差するか判定
bool is_intersect_ss(const Segment& s, const Segment& t) {
  return ccw(s.a, s.b, t.a) * ccw(s.a, s.b, t.b) <= 0 &&
         ccw(t.a, t.b, s.a) * ccw(t.a, t.b, s.b) <= 0;
}
// 直線 l,m の交点を求める
Point cross_point_ll(const Line& l, const Line& m) {
  Real A = cross(l.b - l.a, m.b - m.a);
  Real B = cross(l.b - l.a, l.b - m.a);
  if (equals(abs(A), 0) && equals(abs(B), 0)) return m.a;
  return m.a + (m.b - m.a) * B / A;
}
// 線分 s と点 p の距離を求める
Real distance_sp(const Segment& s, const Point& p) {
  Point r = projection(s, p);
  if (ccw(s.a, s.b, r) == 0) return abs(r - p);
  return min(abs(s.a - p), abs(s.b - p));
}
// 線分 a,b の距離を求める
Real distance_ss(const Segment& a, const Segment& b) {
  if (is_intersect_ss(a, b)) return 0;
  return min({distance_sp(a, b.a), distance_sp(a, b.b), distance_sp(b, a.a),
              distance_sp(b, a.b)});
}

void solve() {
  int n, m, l;
  cin >> n >> m >> l;

  if (n == 0) {
    exit(0);
  }

  double pi = acos(-1);

  vector<vector<Segment>> sgs(n);
  for (int i = 0; i < n; ++i) {
    int x, y, a, r;
    cin >> x >> y >> a >> r;
    vector<Point> pts(5);
    for (int j = 0; j < 5; ++j) {
      double X = x + r * sin((2 * pi * j) / 5 - (pi * a) / 180);
      double Y = y + r * cos((2 * pi * j) / 5 - (pi * a) / 180);
      pts[j] = Point(X, Y);
    }

    for (int j = 0; j < 5; ++j) {
      sgs[i].emplace_back(pts[j], pts[(j + 2) % 5]);
    }
  }

  auto eval = [&](vector<Segment>& a, vector<Segment>& b) {
    double res = 1e9;
    for (int i = 0; i < 5; ++i) {
      for (int j = 0; j < 5; ++j) {
        chmin(res, distance_ss(a[i], b[j]));
      }
    }
    return res;
  };

  vector<vector<double>> dist(n, vector<double>(n, 1e9));
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
      dist[i][j] = eval(sgs[i], sgs[j]);
    }
  }

  for (int k = 0; k < n; ++k) {
    for (int i = 0; i < n; ++i) {
      for (int j = 0; j < n; ++j) {
        chmin(dist[i][j], dist[i][k] + dist[k][j]);
      }
    }
  }

  cout << fixed << setprecision(12);
  cout << dist[m - 1][l - 1] << endl;
}

int main() {
  cin.tie(nullptr);
  ios::sync_with_stdio(false);

  while (true) {
    solve();
  }

  return 0;
}
accuracy: 1
time_ms: 340
memory_kb: 4224
final_rank: 9

code_snippet: #line 2 "/Users/noya2/Desktop/Noya2_library/template/template.hpp"
using namespace std;

#include<bits/stdc++.h>
#line 1 "/Users/noya2/Desktop/Noya2_library/template/inout_old.hpp"
namespace noya2 {

template <typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> &p){
    os << p.first << " " << p.second;
    return os;
}
template <typename T, typename U>
istream &operator>>(istream &is, pair<T, U> &p){
    is >> p.first >> p.second;
    return is;
}

template <typename T>
ostream &operator<<(ostream &os, const vector<T> &v){
    int s = (int)v.size();
    for (int i = 0; i < s; i++) os << (i ? " " : "") << v[i];
    return os;
}
template <typename T>
istream &operator>>(istream &is, vector<T> &v){
    for (auto &x : v) is >> x;
    return is;
}

void in() {}
template <typename T, class... U>
void in(T &t, U &...u){
    cin >> t;
    in(u...);
}

void out() { cout << "\n"; }
template <typename T, class... U, char sep = ' '>
void out(const T &t, const U &...u){
    cout << t;
    if (sizeof...(u)) cout << sep;
    out(u...);
}

template<typename T>
void out(const vector<vector<T>> &vv){
    int s = (int)vv.size();
    for (int i = 0; i < s; i++) out(vv[i]);
}

struct IoSetup {
    IoSetup(){
        cin.tie(nullptr);
        ios::sync_with_stdio(false);
        cout << fixed << setprecision(15);
        cerr << fixed << setprecision(7);
    }
} iosetup_noya2;

} // namespace noya2
#line 1 "/Users/noya2/Desktop/Noya2_library/template/const.hpp"
namespace noya2{

const int iinf = 1'000'000'007;
const long long linf = 2'000'000'000'000'000'000LL;
const long long mod998 =  998244353;
const long long mod107 = 1000000007;
const long double pi = 3.14159265358979323;
const vector<int> dx = {0,1,0,-1,1,1,-1,-1};
const vector<int> dy = {1,0,-1,0,1,-1,-1,1};
const string ALP = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const string alp = "abcdefghijklmnopqrstuvwxyz";
const string NUM = "0123456789";

void yes(){ cout << "Yes\n"; }
void no(){ cout << "No\n"; }
void YES(){ cout << "YES\n"; }
void NO(){ cout << "NO\n"; }
void yn(bool t){ t ? yes() : no(); }
void YN(bool t){ t ? YES() : NO(); }

} // namespace noya2
#line 2 "/Users/noya2/Desktop/Noya2_library/template/utils.hpp"

#line 6 "/Users/noya2/Desktop/Noya2_library/template/utils.hpp"

namespace noya2{

unsigned long long inner_binary_gcd(unsigned long long a, unsigned long long b){
    if (a == 0 || b == 0) return a + b;
    int n = __builtin_ctzll(a); a >>= n;
    int m = __builtin_ctzll(b); b >>= m;
    while (a != b) {
        int mm = __builtin_ctzll(a - b);
        bool f = a > b;
        unsigned long long c = f ? a : b;
        b = f ? b : a;
        a = (c - b) >> mm;
    }
    return a << std::min(n, m);
}

template<typename T> T gcd_fast(T a, T b){ return static_cast<T>(inner_binary_gcd(std::abs(a),std::abs(b))); }

long long sqrt_fast(long long n) {
    if (n <= 0) return 0;
    long long x = sqrt(n);
    while ((x + 1) * (x + 1) <= n) x++;
    while (x * x > n) x--;
    return x;
}

template<typename T> T floor_div(const T n, const T d) {
    assert(d != 0);
    return n / d - static_cast<T>((n ^ d) < 0 && n % d != 0);
}

template<typename T> T ceil_div(const T n, const T d) {
    assert(d != 0);
    return n / d + static_cast<T>((n ^ d) >= 0 && n % d != 0);
}

template<typename T> void uniq(std::vector<T> &v){
    std::sort(v.begin(),v.end());
    v.erase(unique(v.begin(),v.end()),v.end());
}

template <typename T, typename U> inline bool chmin(T &x, U y) { return (y < x) ? (x = y, true) : false; }

template <typename T, typename U> inline bool chmax(T &x, U y) { return (x < y) ? (x = y, true) : false; }

template<typename T> inline bool range(T l, T x, T r){ return l <= x && x < r; }

} // namespace noya2
#line 8 "/Users/noya2/Desktop/Noya2_library/template/template.hpp"

#define rep(i,n) for (int i = 0; i < (int)(n); i++)
#define repp(i,m,n) for (int i = (m); i < (int)(n); i++)
#define reb(i,n) for (int i = (int)(n-1); i >= 0; i--)
#define all(v) (v).begin(),(v).end()

using ll = long long;
using ld = long double;
using uint = unsigned int;
using ull = unsigned long long;
using pii = pair<int,int>;
using pll = pair<ll,ll>;
using pil = pair<int,ll>;
using pli = pair<ll,int>;

namespace noya2{

/*　~ (. _________ . /)　*/

}

using namespace noya2;


#line 2 "c.cpp"

#line 2 "/Users/noya2/Desktop/Noya2_library/geometry/float_geometry.hpp"

#include <type_traits>
#include <concepts>
#include <compare>
#line 12 "/Users/noya2/Desktop/Noya2_library/geometry/float_geometry.hpp"
#include <ranges>

#if __has_include(<boost/multiprecision/cpp_bin_float.hpp>)

#include<boost/multiprecision/cpp_bin_float.hpp>

#endif

namespace float_geometry {

template<class T>
struct is_floating_point_number : std::bool_constant<false> {};

template<std::floating_point F>
struct is_floating_point_number<F> : std::bool_constant<true> {};

#if __has_include(<boost/multiprecision/cpp_bin_float.hpp>)

template<unsigned D>
struct is_floating_point_number<boost::multiprecision::number<boost::multiprecision::backends::cpp_bin_float<D>>> : std::bool_constant<true> {};

template<unsigned D>
struct is_floating_point_number<boost::multiprecision::number<boost::multiprecision::backends::cpp_dec_float<D>>> : std::bool_constant<true> {};

#endif

template<class F>
concept FloatingPoint = is_floating_point_number<F>::value;

template<FloatingPoint Float>
constexpr Float EPS = Float{1e-8};

template<FloatingPoint Float>
constexpr int sign(const Float &x, Float eps = EPS<Float>){
    return (x < -eps ? -1 : x > eps ? 1 : 0);
}

template<FloatingPoint Float>
struct point {
    Float x, y;
    point () {}
    point (Float _x, Float _y) : x(_x), y(_y) {}
    point &operator+=(const point &r){
        x += r.x;
        y += r.y;
        return *this;
    }
    point &operator-=(const point &r){
        x -= r.x;
        y -= r.y;
        return *this;
    }
    point operator+() const {
        return *this;
    }
    point operator-() const {
        x = -x;
        y = -y;
        return *this;
    }
    point &operator*=(const Float &a){
        x *= a;
        y *= a;
        return *this;
    }
	point &operator/=(const Float &a){
        x /= a;
        y /= a;
        return *this;
    }
    friend point operator+(const point& lhs, const point& rhs){
        return point(lhs) += rhs;
    }
    friend point operator-(const point& lhs, const point& rhs){
        return point(lhs) -= rhs;
    }
    friend point operator*(const point& lhs, const Float& a){
        return point(lhs) *= a;
    }
    friend point operator*(const Float& a, const point& rhs){
        return point(rhs) *= a;
    }
	friend point operator/(const point& lhs, const Float& a){
        return point(lhs) /= a;
    }
    auto operator<=>(const point&) const = default;
    // friend bool operator<(const point &lhs, const point &rhs){
    //     if (lhs.x == rhs.x) return lhs.y < rhs.y;
    //     return lhs.x < rhs.x;
    // }
    // friend bool operator==(const point &lhs, const point &rhs){
    //     return (lhs.x == rhs.x && lhs.y == rhs.y);
    // }
    friend std::ostream &operator<<(std::ostream &os, const point& p){
        return os << p.x << ' ' << p.y;
    }
    friend std::istream &operator>>(std::istream &is, point &a){
        Float _x, _y; is >> _x >> _y;
        a = point(_x, _y);
        return (is);
    }
    friend Float norm(const point &a) {
        return a.x*a.x + a.y*a.y;
    }
    friend Float dot(const point &a, const point &b){
        return a.x*b.x + a.y*b.y;
    }
    friend Float cross(const point &a, const point &b){
        return a.x*b.y - a.y*b.x;
    }
	friend point rot90(const point &a){
		return point(-a.y, a.x);
	}
    friend point rot(const point &a, const Float rad){
        using std::cos, std::sin;
        Float cosrad = cos(rad);
        Float sinrad = sin(rad);
        return point(a.x * cosrad - a.y * sinrad, a.x * sinrad + a.y * cosrad);
    }
    friend Float abs(const point &a){
        using std::sqrt;
        return sqrt(norm(a));
    }

    friend int quadrant_atan2(const point &a){
        // not origin point
        // ceil ( atan2(y,x) / (pi/2) )
		int signx = sign(a.x);
		int signy = sign(a.y);
		if (signx <= 0 && signy <  0) return -1;
		if (signx >  0 && signy <= 0) return  0;
		if (signx >= 0 && signy >  0) return  1;
		if (signx <  0 && signy >= 0) return  2;
        // origin point x == 0 && y == 0
        return 0;
    }

    friend int sign_cross(const point &a, const point &b){
        using std::abs;
        return sign(cross(a, b), EPS<Float> * (abs(a.x) + abs(a.y) + abs(b.x) + abs(b.y)));
        // return sign(cross(a, b), EPS<Float> * (abs<Float>(a.x) + abs<Float>(a.y) + abs<Float>(b.x) + abs<Float>(b.y)));
    }

    friend int sign_dot(const point &a, const point &b){
        using std::abs;
        return sign(dot(a, b), EPS<Float> * (abs(a.x) + abs(a.y) + abs(b.x) + abs(b.y)));
        // return sign(dot(a, b), EPS<Float> * (abs<Float>(a.x) + abs<Float>(a.y) + abs<Float>(b.x) + abs<Float>(b.y)));
    }

	friend int ccw(const point &a, point b, point c){
		b -= a;
		c -= a;
		int signcr = sign_cross(b, c);
		if (signcr > 0){
			// ccw       
			//           c
			// a --> b 
			return 1;
		}
		if (signcr < 0){
			// cw
			// a --> b
			//           c
			return -1;
		}
		if (sign_dot(b, c) < 0){
			// c  a --> b
			return 2;
		}
		if (norm(b) < norm(c)){
			// a --> b   c
			return -2;
		}
		// a - c -> b
		return 0;
	}
    friend bool has_common_point(const point &a, const point &b){
        return sign(a.x - b.x) == 0 && sign(a.y - b.y) == 0;
    }

    ld arg() const {
        std::complex<ld> a(x, y);
        return std::arg(a);
    }
};

template<FloatingPoint Float>
struct arg_less {
    constexpr bool operator()(const point<Float> &l, const point<Float> &r){
        using std::atan2;
        return atan2(l.y, l.x) < atan2(r.y, r.x);
    }
};

template<FloatingPoint Float>
void arg_sort(std::vector<point<Float>> &a){
	std::sort(a.begin(), a.end(), arg_less<Float>{});
}


template<FloatingPoint Float>
std::vector<int> upper_convex_hull_index(const std::vector<point<Float>> &a){
	if (a.empty()) return {};
	std::vector<int> ids(a.size());
    std::iota(ids.begin(), ids.end(), 0);
	std::sort(ids.begin(), ids.end(), [&](int l, int r){
		return a[l] < a[r];
	});
	std::vector<int> st(a.size());
	int ptr = 0;
	for (int i : ids){
		if (ptr >= 1 && a[st[ptr-1]].x == a[i].x) ptr--;
		while (ptr >= 2){
			int c = st[ptr-1];
			int p = st[ptr-2];
			if (sign_cross(a[i] - a[c], a[c] - a[p]) > 0){
				break;
			}
			ptr--;
		}
		st[ptr++] = i;
	}
	st.resize(ptr);
	return st;
}

template<FloatingPoint Float>
std::vector<int> lower_convex_hull_index(const std::vector<point<Float>> &a){
	if (a.empty()) return {};
	std::vector<int> ids(a.size());
    std::iota(ids.begin(), ids.end(), 0);
	std::sort(ids.begin(), ids.end(), [&](int l, int r){
		return a[l] < a[r];
	});
	std::vector<int> st(a.size());
	int ptr = 0;
	for (int i : ids){
		if (ptr >= 1 && a[st[ptr-1]].x == a[i].x) continue;
		while (ptr >= 2){
			int c = st[ptr-1];
			int p = st[ptr-2];
			if (sign_cross(a[c] - a[p], a[i] - a[c]) > 0){
				break;
			}
			ptr--;
		}
		st[ptr++] = i;
	}
	st.resize(ptr);
	return st;
}

template<FloatingPoint Float>
std::vector<int> convex_hull_index(const std::vector<point<Float>> &a){
	if (a.empty()) return {};
	auto upper = upper_convex_hull_index(a);
	auto lower = lower_convex_hull_index(a);
	if (upper.size() == 1u){
		// lower.size() == 1u
		if (a[upper.front()] == a[lower.front()]){
			return {upper.front()};
		}
		return {lower.front(), upper.front()};
	}
	if (a[upper.back()] == a[lower.back()]){
		lower.pop_back();
	}
	lower.insert(lower.end(), upper.rbegin(), upper.rend());
	if (a[upper.front()] == a[lower.front()]) lower.pop_back();
	return lower;
}


template<FloatingPoint Float>
struct line {
	point<Float> end0, end1;
    line () {}
	line (const point<Float> &_end0, const point<Float> &_end1) : end0(_end0), end1(_end1) {
		assert(end0 != end1);
	}
	// auto operator<=>(const line &) const = default;
	point<Float> direction() const {
		return end1 - end0;
	}
    friend bool is_parallel(const line &a, const line &b){
        return sign_cross(a.direction(), b.direction()) == 0;
    }
    friend bool is_orthgonal(const line &a, const line &b){
        return sign_dot(a.direction(), b.direction()) == 0;
    }
	friend bool has_common_point(const line &a, const point<Float> &b){
		return sign(cross(a.direction(), b - a.end0)) == 0;
	}
	friend bool has_common_point(const line &a, const line &b){
		return !is_parallel(a, b) || has_common_point(a, b.end0);
	}
	friend point<Float> common_point(const line &a, const line &b){
		// assert(has_common_point(a, b));
		if (is_parallel(a, b)){
			return a.end0;
		}
		return a.end0 + a.direction() * cross(b.end0 - a.end0, b.end1 - b.end0) / cross(a.direction(), b.direction());
	}
	friend point<Float> projection(const line &a, const point<Float> &b){
		auto dir = a.direction();
		return a.end0 + dir * (dot(dir, b - a.end0) / norm(dir));
	}
	friend point<Float> reflection(const line &a, const point<Float> &b){
		auto prj = projection(a, b);
		return prj + prj - b;
	}
    friend line<Float> perpendicular_bisector(const point<Float> &p1, const point<Float> &p2){
        auto ctr = (p1 + p2) / Float{2};
        auto dir = rot90(p2 - p1);
        return line<Float>(ctr, ctr + dir);
    }
};

template<FloatingPoint Float>
line<Float> perpendicular_bisector(const point<Float> &p1, const point<Float> &p2){
    auto ctr = (p1 + p2) / Float{2};
    auto dir = rot90(p2 - p1);
    return line<Float>(ctr, ctr + dir);
}


template<FloatingPoint Float>
struct segment {
	point<Float> end0, end1;
    segment () {}
	segment (const point<Float> &_end0, const point<Float> &_end1) : end0(_end0), end1(_end1) {
		assert(end0 != end1);
	}
	// auto operator<=>(const segment &) const = default;
	point<Float> direction() const {
		return end1 - end0;
	}
    friend bool is_parallel(const segment &a, const segment &b){
        return sign_cross(a.direction(), b.direction()) == 0;
    }
    friend bool is_orthgonal(const segment &a, const segment &b){
        return sign_dot(a.direction(), b.direction()) == 0;
    }
	friend bool has_common_point(const segment &a, const segment &b){
		return ccw(a.end0, a.end1, b.end0) * ccw(a.end0, a.end1, b.end1) <= 0
			&& ccw(b.end0, b.end1, a.end0) * ccw(b.end0, b.end1, a.end1) <= 0;
	}
	friend bool has_common_point(const segment &a, const point<Float> &b){
		return ccw(a.end0, a.end1, b) == 0;
	}
	friend point<Float> common_point(const segment &a, const segment &b){
		// assert(has_common_point(a, b));
		if (is_parallel(a, b)){
			if (has_common_point(a, b.end0)){
				return b.end0;
			}
			if (has_common_point(a, b.end1)){
				return b.end1;
			}
			return a.end0;
		}
		return a.end0 + a.direction() * cross(b.end0 - a.end0, b.end1 - b.end0) / cross(a.direction(), b.direction());
	}
	line<Float> as_line() const {
		return line<Float>(end0, end1);
	}
};

} // float_geometry
#line 4 "c.cpp"

using namespace float_geometry;

#line 2 "/Users/noya2/Desktop/Noya2_library/graph/graph_query.hpp"

#line 2 "/Users/noya2/Desktop/Noya2_library/data_structure/csr.hpp"

#line 7 "/Users/noya2/Desktop/Noya2_library/data_structure/csr.hpp"

namespace noya2::internal {

template<class E>
struct csr {
    csr () {}
    csr (int _n) : n(_n) {}
    csr (int _n, int m) : n(_n){
        start.reserve(m);
        elist.reserve(m);
    }
    // ACL style constructor (do not have to call build)
    csr (int _n, const std::vector<std::pair<int,E>> &idx_elem) : n(_n), start(_n + 2), elist(idx_elem.size()) {
        for (auto &[i, e] : idx_elem){
            start[i + 2]++;
        }
        for (int i = 1; i < n; i++){
            start[i + 2] += start[i + 1];
        }
        for (auto &[i, e] : idx_elem){
            elist[start[i + 1]++] = e;
        }
        prepared = true;
    }
    int add(int idx, E elem){
        int eid = start.size();
        start.emplace_back(idx);
        elist.emplace_back(elem);
        return eid;
    }
    void build(){
        if (prepared) return ;
        int m = start.size();
        std::vector<E> nelist(m);
        std::vector<int> nstart(n + 2, 0);
        for (int i = 0; i < m; i++){
            nstart[start[i] + 2]++;
        }
        for (int i = 1; i < n; i++){
            nstart[i + 2] += nstart[i + 1];
        }
        for (int i = 0; i < m; i++){
            nelist[nstart[start[i] + 1]++] = elist[i];
        }
        swap(elist,nelist);
        swap(start,nstart);
        prepared = true;
    }
    const auto operator[](int idx) const {
        return std::ranges::subrange(elist.begin()+start[idx],elist.begin()+start[idx+1]);
    }
    auto operator[](int idx){
        return std::ranges::subrange(elist.begin()+start[idx],elist.begin()+start[idx+1]);
    }
    const auto operator()(int idx, int l, int r) const {
        return std::ranges::subrange(elist.begin()+start[idx]+l,elist.begin()+start[idx]+r);
    }
    auto operator()(int idx, int l, int r){
        return std::ranges::subrange(elist.begin()+start[idx]+l,elist.begin()+start[idx]+r);
    }
    size_t size() const {
        return n;
    }
    int n;
    std::vector<int> start;
    std::vector<E> elist;
    bool prepared = false;
};

} // namespace noya2::internal
#line 2 "/Users/noya2/Desktop/Noya2_library/graph/unweighted_type.hpp"

namespace noya2 {

struct unweighted {};

} // namespace noya2
#line 6 "/Users/noya2/Desktop/Noya2_library/graph/graph_query.hpp"

#line 12 "/Users/noya2/Desktop/Noya2_library/graph/graph_query.hpp"

namespace noya2 {

template<typename Cost>
struct graph {
    int n;
    internal::csr<std::pair<int,Cost>> g;
    Cost dist_inf = std::numeric_limits<Cost>::max() / 3;
    graph (int _n = 0) : n(_n), g(_n) {}
    graph (int _n, int _m) : n(_n), g(_n,_m) {}
    // 有向辺を追加 (無向辺ではないことに注意！)
    int add_edge(int u, int v, Cost cost = 1){
        int id = g.add(u, {v,cost});
        return id;
    }
    template<bool directed>
    static graph input(int _n, int _m, int indexed = 1){
        if constexpr (directed){
            graph g(_n, _m*2);
            for (int i = 0; i < _m; i++){
                int u, v; std::cin >> u >> v;
                u -= indexed, v -= indexed;
                Cost c; std::cin >> c;
                g.add_edge(u, v, c);
                g.add_edge(v, u, c);
            }
            g.build();
            return g;
        }
        else {
            graph g(_n, _m);
            for (int i = 0; i < _m; i++){
                int u, v; std::cin >> u >> v;
                u -= indexed, v -= indexed;
                Cost c; std::cin >> c;
                g.add_edge(u, v, c);
            }
            g.build();
            return g;
        }
    }
    void build(){
        g.build();
    }
    void set_inf(Cost new_inf){
        dist_inf = new_inf;
    }
    std::vector<Cost> dijkstra(int s){
        g.build();
        std::vector<Cost> dist(n,dist_inf);
        dist[s] = 0;
        using P = std::pair<Cost,int>;
        std::priority_queue<P,std::vector<P>,std::greater<P>> pque;
        pque.push(P(0,s));
        while (!pque.empty()){
            auto [d, v] = pque.top(); pque.pop();
            if (dist[v] < d-ld(1e-8)) continue;
            for (auto [u, c] : g[v]){
                if (chmin(dist[u],d+c)){
                    pque.push(P(dist[u],u));
                }
            }
        }
        return dist;
    }
    std::vector<int> reconstruct(int s, int t, const std::vector<Cost> &dist){
        if (dist[t] == dist_inf) return {};
        g.build();
        std::vector<int> from(n,-1);
        std::queue<int> que;
        que.push(s);
        while (!que.empty()){
            int v = que.front(); que.pop();
            for (auto [u, c] : g[v]){
                if (from[u] == -1 && dist[u] == dist[v] + c){
                    from[u] = v;
                    que.push(u);
                }
            }
        }
        std::vector<int> ans = {t};
        while (t != s){
            t = from[t];
            ans.emplace_back(t);
        }
        std::reverse(ans.begin(),ans.end());
        return ans;
    }
    std::vector<Cost> bfs01(int s){
        g.build();
        std::vector<Cost> dist(n,dist_inf);
        dist[s] = 0;
        std::deque<int> que;
        que.push_back(s);
        while (!que.empty()){
            int v = que.front(); que.pop_front();
            for (auto [u, c] : g[v]){
                if (chmin(dist[u],dist[v]+c)){
                    if (c == 0) que.push_front(u);
                    else que.push_back(u);
                }
            }
        }
        return dist;
    }
    std::vector<Cost> bfs1(int s){
        g.build();
        std::vector<Cost> dist(n,dist_inf);
        dist[s] = 0;
        std::queue<int> que;
        que.push(s);
        while (!que.empty()){
            int v = que.front(); que.pop();
            for (auto [u, c] : g[v]){
                if (chmin(dist[u],dist[v]+c)){
                    que.push(u);
                }
            }
        }
        return dist;
    }
    std::vector<Cost> bellman_ford(int s, bool &ng_cycle){
        g.build();
        std::vector<Cost> dist(n,dist_inf);
        std::vector<int> ng;
        dist[s] = 0;
        int tm = 0;
        while (tm < n){
            bool finish = true;
            for (int v = 0; v < n; v++){
                if (dist[v] == dist_inf) continue;
                for (auto [u, c] : g[v]){
                    if (chmin(dist[u],dist[v]+c)){
                        finish = false;
                        if (tm == n-1) ng.emplace_back(u);
                    }
                }
            }
            if (finish) break;
            tm++;
        }
        ng_cycle = (tm == n);
        if (ng_cycle){
            for (auto v : ng) dist[v] = -dist_inf;
            tm = n;
            while (tm--){
                for (int v = 0; v < n; v++){
                    if (dist[v] != -dist_inf) continue;
                    for (auto [u, c] : g[v]){
                        dist[u] = -dist_inf;
                    }
                }
            }
        }
        return dist;
    }
    std::vector<std::vector<Cost>> warshall_floyd(){
        g.build();
        std::vector<std::vector<Cost>> dist(n,std::vector<Cost>(n,dist_inf));
        for (int v = 0; v < n; v++){
            dist[v][v] = 0;
            for (auto [u, c] : g[v]){
                chmin(dist[v][u],c);
            }
        }
        for (int k = 0; k < n; k++){
            for (int i = 0; i < n; i++){
                for (int j = 0; j < n; j++){
                    chmin(dist[i][j],dist[i][k]+dist[k][j]);
                }
            }
        }
        return dist;
    }
    const auto operator[](int idx) const { return g[idx]; }
    auto operator[](int idx) { return g[idx]; }
};


} // namespace noya2
#line 8 "c.cpp"

point<ld> proj(const line<ld> &l, const point<ld> &p) {
    auto ab = l.end1 - l.end0;
    return l.end0 + ab * (dot(ab, p - l.end0) / norm(ab));
}

ld calc2(segment<ld> a, point<ld> b){
    ld len = abs(a.end0 - a.end1);
    auto p = proj(a.as_line(), b);
    ld d1 = abs(a.end0 - p);
    ld d2 = abs(a.end1 - p);
    if (sign(d1+d2-len) == 0){
        return abs(b-p);
    }
    return min(abs(a.end0-b),abs(a.end1-b));
}

ld calc(segment<ld> a, point<ld> b){
    ld le = 0, ri = 1;
    int many = 60;
    auto val = [&](ld p){
        return norm(a.end0 * p + a.end1 * (1-p) - b);
    };
    while (many--){
        ld m1 = (le + le + ri) / 3;
        ld m2 = (le + ri + ri) / 3;
        if (val(m1) < val(m2)){
            ri = m2;
        }
        else {
            le = m1;
        }
    }
    return sqrt(val(le));
}

ld dist(segment<ld> a, segment<ld> b){
    if (has_common_point(a,b)){
        return 0;
    }
    ld ret = 1e18;
    rep(i,2){
        rep(j,2){
            chmin(ret,calc2(b,a.end0));
            swap(a.end0,a.end1);
        }
        swap(a,b);
    }
    return ret;
}


void solve1(int n, int m, int l){
    m--, l--;
    vector<point<ld>> ctr(n);
    vector<ld> a(n), r(n);
    rep(i,n){
        in(ctr[i]);
        in(a[i],r[i]);
    }
    graph<ld> g(n*6);
    g.set_inf(1e18);
    auto idx = [&](int i, int e){
        return i * 6 + e;
    };
    rep(i,n){
        rep(j,5){
            g.add_edge(idx(i,j),idx(i,5),0);
            g.add_edge(idx(i,5),idx(i,j),0);
        }
    }
    vector<vector<point<ld>>> vs(n,vector<point<ld>>(5));
    rep(i,n) rep(j,5){
        ld th = pi/2 + a[i]*pi/180 + 4./5*pi * j;
        vs[i][j] = ctr[i] + rot(point<ld>(r[i],0),th);
    }
    vector<vector<segment<ld>>> segs(n,vector<segment<ld>>(5));
    rep(i,n) rep(j,5){
        segs[i][j] = segment<ld>(vs[i][j],vs[i][(j+1)%5]);
    }
    rep(i,n) rep(j,5) rep(ii,n) rep(jj,5){
        g.add_edge(idx(i,j),idx(ii,jj),dist(segs[i][j],segs[ii][jj]));
    }
    ld ans = g.dijkstra(idx(m,5))[idx(l,5)];
    out(ans);
}

void solve(){
    int n, m, l;
    while (true){
        in(n,m,l);
        if (n == 0) return ;
        solve1(n,m,l);
    }
}

int main(){
    int t = 1; //in(t);
    while (t--) { solve(); }
}
accuracy: 1
time_ms: 810
memory_kb: 28104
final_rank: 20

code_snippet: // #pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
using namespace std;

using ll = long long;
using ull = unsigned long long;
using vecint = std::vector<int>;
using vecll = std::vector<long long>;
using vecstr = std::vector<string>;
using vecbool = std::vector<bool>;
using vecdou = std::vector<double>;
using vecpl = std::vector<pair<ll,ll>>;
using vec2d = std::vector<vecll>;
using vec2di = std::vector<vecint>;
using vec2dd = std::vector<vecdou>;
using vec2db = std::vector<vecbool>;
using pl = pair<long long,long long>;
using mint998 = modint998244353;
using mint107 = modint1000000007;
using mint = modint;
using vecmint = std::vector<mint>;
using vecmint998 = std::vector<mint998>;
using vecmint107 = std::vector<mint107>;
using vec2dmint = std::vector<vecmint>; 
using vec2dmint998 = std::vector<vecmint998>;
using vec2dmint107 = std::vector<vecmint107>;
#define rep(i,n) for (ll i = 0; i < (ll)(n); i++)
#define rep1(i,n) for (ll i = 1; i <= (ll)(n); i++)
#define REP(i,l,r) for (ll i = (ll)(l); i < (ll)(r); i++)
#define rrep(i,n) for (ll i = (ll)(n)-1; i >= 0; i--)
#define rrep1(i,n) for (ll i = (ll)(n); i > 0; i--)
#define RREP(i,l,r) for (ll i = (ll)(r)-1; i >= (ll)(l); i--)
#define all(a) (a).begin(), (a).end()
#define INF ((1LL<<62)-(1LL<<31))
#define inr(a,x,b) ((a) <= (x) && (x) < (b))
template <typename T>
bool chmax(T &a, const T &b) {
    if (a < b) {
        a = b;
        return true;
    }
    return false;
}
template <typename T>
bool chmin(T &a, const T &b) {
    if (a > b) {
        a = b;
        return true;
    }
    return false;
}

void ynout(bool x,string Tru="Yes",string Wro="No"){
    if(x){
        cout << Tru << '\n';
    }else{
        cout << Wro << '\n';
    }
}
ll power(ll a,ll b,ll mod=INF){
    long long x=1,y=a%mod;
    while(b>0){
        if(b&1ll){
            x=(x*y)%mod;
        }
        y=(y*y)%mod;
        b>>=1;
    }
  return x%mod;
}
ll Pdist2(pair<ll,ll> a,pair<ll,ll> b){
    return (a.first-b.first)*(a.first-b.first)+(a.second-b.second)*(a.second-b.second);
}
double Pdist(pair<ll,ll> a,pair<ll,ll> b){
    return sqrt(Pdist2(a,b));
}
ll PdistM(pair<ll,ll> a,pair<ll,ll> b){
    return abs(a.first-b.first)+abs(a.second-b.second);
}
ll gcd(ll a,ll b){
    if(b==0){
        return a;
    }else{
        return gcd(b,a%b);
    }
}
ll lcm(ll a,ll b){
    return a/gcd(a,b)*b;
}

namespace geometry {
    // Point : 複素数型を位置ベクトルとして扱う
    // 実軸(real)をx軸、挙軸(imag)をy軸として見る
    using D = double;
    using Point = std::complex<D>;
    const D EPS = 1e-7;
    const D PI = std::acos(D(-1));

    inline bool equal(const D &a, const D &b) { return std::fabs(a - b) < EPS; }

    // 単位ベクトル(unit vector)を求める
    Point unitVector(const Point &a) { return a / std::abs(a); }

    // 法線ベクトル(normal vector)を求める
    // 90度回転した単位ベクトルをかける
    // -90度がよければPoint(0, -1)をかける
    Point normalVector(const Point &a) { return a * Point(0, 1); }

    // 内積(dot product) : a・b = |a||b|cosΘ
    D dot(const Point &a, const Point &b) {
        return (a.real() * b.real() + a.imag() * b.imag());
    }

    // 外積(cross product) : a×b = |a||b|sinΘ
    D cross(const Point &a, const Point &b) {
        return (a.real() * b.imag() - a.imag() * b.real());
    }

    // 点pを反時計回りにtheta度回転
    // thetaはラジアン！！！
    Point rotate(const Point &p, const D &theta) {
        return Point(std::cos(theta) * p.real() - std::sin(theta) * p.imag(),
                     std::sin(theta) * p.real() + std::cos(theta) * p.imag());
    }

    // ラジアン->度
    D radianToDegree(const D &radian) { return radian * 180.0 / PI; }

    // 度->ラジアン
    D degreeToRadian(const D &degree) { return degree * PI / 180.0; }

    // 点の回転方向
    // 点a, b, cの位置関係について(aが基準点)
    int ccw(const Point &a, Point b, Point c) {
        b -= a, c -= a;
        // 点a, b, c が
        // 反時計回りの時、
        if(cross(b, c) > EPS) return 1;
        // 時計回りの時、
        if(cross(b, c) < -EPS) return -1;
        // c, a, bがこの順番で同一直線上にある時、
        if(dot(b, c) < 0) return 2;
        // a, b, cがこの順番で同一直線上にある場合、
        if(std::norm(b) < std::norm(c)) return -2;
        // cが線分ab上にある場合、
        return 0;
    }

    // Line : 直線を表す構造体
    // b - a で直線・線分を表せる
    struct Line {
        Point a, b;
        Line() = default;
        Line(Point a, Point b) : a(a), b(b) {}
        // Ax+By=C
        Line(D A, D B, D C) {
            if(equal(A, 0)) {
                a = Point(0, C / B), b = Point(1, C / B);
            } else if(equal(B, 0)) {
                a = Point(C / A, 0), b = Point(C / A, 1);
            } else if(equal(C, 0)) {
                a = Point(0, C / B), b = Point(1, (C - A) / B);
            } else {
                a = Point(0, C / B), b = Point(C / A, 0);
            }
        }
    };

    // Segment : 線分を表す構造体
    // Lineと同じ
    struct Segment : Line {
        Segment() = default;

        Segment(Point a, Point b) : Line(a, b) {}
        D get_dist() { return std::abs(a - b); }
    };

    // Circle : 円を表す構造体
    // pが中心の位置ベクトル、rは半径
    struct Circle {
        Point p;
        D r;

        Circle() = default;

        Circle(Point p, D r) : p(p), r(r) {}
    };

    // 2直線の直交判定 : a⊥b <=> dot(a, b) = 0
    bool isOrthogonal(const Line &a, const Line &b) {
        return equal(dot(a.b - a.a, b.b - b.a), 0);
    }
    // 2直線の平行判定 : a//b <=> cross(a, b) = 0
    bool isParallel(const Line &a, const Line &b) {
        return equal(cross(a.b - a.a, b.b - b.a), 0);
    }

    // 点cが直線ab上にあるか
    bool isPointOnLine(const Point &a, const Point &b, const Point &c) {
        return isParallel(Line(a, b), Line(a, c));
    }

    // 点cが"線分"ab上にあるか
    bool isPointOnSegment(const Point &a, const Point &b, const Point &c) {
        // |a-c| + |c-b| <= |a-b| なら線分上
        return (std::abs(a - c) + std::abs(c - b) < std::abs(a - b) + EPS);
    }

    // 直線lと点pの距離を求める
    D distanceBetweenLineAndPoint(const Line &l, const Point &p) {
        return std::abs(cross(l.b - l.a, p - l.a)) / std::abs(l.b - l.a);
    }

    // 線分lと点pの距離を求める
    // 定義：点pから「線分lのどこか」への最短距離
    D distanceBetweenSegmentAndPoint(const Segment &l, const Point &p) {
        if(dot(l.b - l.a, p - l.a) < EPS) return std::abs(p - l.a);
        if(dot(l.a - l.b, p - l.b) < EPS) return std::abs(p - l.b);
        return std::abs(cross(l.b - l.a, p - l.a)) / std::abs(l.b - l.a);
    }

    // 直線s, tの交点の計算
    Point crossPoint(const Line &s, const Line &t) {
        D d1 = cross(s.b - s.a, t.b - t.a);
        D d2 = cross(s.b - s.a, s.b - t.a);
        if(equal(std::abs(d1), 0) && equal(std::abs(d2), 0)) return t.a;
        return t.a + (t.b - t.a) * (d2 / d1);
    }

    // 線分s, tの交点の計算
    Point crossPoint(const Segment &s, const Segment &t) {
        return crossPoint(Line(s), Line(t));
    }

    // 線分sと線分tが交差しているかどうか
    // bound:線分の端点を含むか
    bool isIntersect(const Segment &s, const Segment &t, bool bound) {
        return ccw(s.a, s.b, t.a) * ccw(s.a, s.b, t.b) < bound &&
               ccw(t.a, t.b, s.a) * ccw(t.a, t.b, s.b) < bound;
    }

    // 線分sとtの距離
    D distanceBetweenSegments(const Segment &s, const Segment &t) {
        if(isIntersect(s, t, 1)) return (D)(0);
        D ans = distanceBetweenSegmentAndPoint(s, t.a);
        ans = std::min(ans, distanceBetweenSegmentAndPoint(s, t.b));
        ans = std::min(ans, distanceBetweenSegmentAndPoint(t, s.a));
        ans = std::min(ans, distanceBetweenSegmentAndPoint(t, s.b));
        return ans;
    }

    // 射影(projection)
    // 直線(線分)lに点pから引いた垂線の足を求める
    Point projection(const Line &l, const Point &p) {
        D t = dot(p - l.a, l.a - l.b) / std::norm(l.a - l.b);
        return l.a + (l.a - l.b) * t;
    }

    Point projection(const Segment &l, const Point &p) {
        D t = dot(p - l.a, l.a - l.b) / std::norm(l.a - l.b);
        return l.a + (l.a - l.b) * t;
    }

    // 反射(reflection)
    // 直線lを対称軸として点pと線対称の位置にある点を求める
    Point reflection(const Line &l, const Point &p) {
        return p + (projection(l, p) - p) * (D)2.0;
    }

    // 2つの円の交差判定
    // 返り値は共通接線の数
    int isIntersect(const Circle &c1, const Circle &c2) {
        D d = std::abs(c1.p - c2.p);
        // 2つの円が離れている場合
        if(d > c1.r + c2.r + EPS) return 4;
        // 外接している場合
        if(equal(d, c1.r + c2.r)) return 3;
        // 内接している場合
        if(equal(d, std::abs(c1.r - c2.r))) return 1;
        // 内包している場合
        if(d < std::abs(c1.r - c2.r) - EPS) return 0;
        return 2;
    }

    // 2つの円の交点
    std::vector<Point> crossPoint(const Circle &c1, const Circle &c2) {
        std::vector<Point> res;
        int mode = isIntersect(c1, c2);
        // 2つの中心の距離
        D d = std::abs(c1.p - c2.p);
        // 2円が離れている場合
        if(mode == 4) return res;
        // 1つの円がもう1つの円に内包されている場合
        if(mode == 0) return res;
        // 2円が外接する場合
        if(mode == 3) {
            D t = c1.r / (c1.r + c2.r);
            res.emplace_back(c1.p + (c2.p - c1.p) * t);
            return res;
        }
        // 内接している場合
        if(mode == 1) {
            if(c2.r < c1.r - EPS) {
                res.emplace_back(c1.p + (c2.p - c1.p) * (c1.r / d));
            } else {
                res.emplace_back(c2.p + (c1.p - c2.p) * (c2.r / d));
            }
            return res;
        }
        // 2円が重なる場合
        D rc1 = (c1.r * c1.r + d * d - c2.r * c2.r) / (2 * d);
        D rs1 = std::sqrt(c1.r * c1.r - rc1 * rc1);
        if(c1.r - std::abs(rc1) < EPS) rs1 = 0;
        Point e12 = (c2.p - c1.p) / std::abs(c2.p - c1.p);
        res.emplace_back(c1.p + rc1 * e12 + rs1 * e12 * Point(0, 1));
        res.emplace_back(c1.p + rc1 * e12 + rs1 * e12 * Point(0, -1));
        return res;
    }

    // 点pが円cの内部(円周上も含む)に入っているかどうか
    bool isInCircle(const Circle &c, const Point &p) {
        D d = std::abs(c.p - p);
        return (equal(d, c.r) || d < c.r - EPS);
    }

    // 円cと直線lの交点
    std::vector<Point> crossPoint(const Circle &c, const Line &l) {
        std::vector<Point> res;
        D d = distanceBetweenLineAndPoint(l, c.p);
        // 交点を持たない
        if(d > c.r + EPS) return res;
        // 接する
        Point h = projection(l, c.p);
        if(equal(d, c.r)) {
            res.emplace_back(h);
            return res;
        }
        Point e = unitVector(l.b - l.a);
        D ph = std::sqrt(c.r * c.r - d * d);
        res.emplace_back(h - e * ph);
        res.emplace_back(h + e * ph);
        return res;
    }

    // 点pを通る円cの接線
    // 2本あるので、接点のみを返す
    std::vector<Point> tangentToCircle(const Point &p, const Circle &c) {
        return crossPoint(c,
                          Circle(p, std::sqrt(std::norm(c.p - p) - c.r * c.r)));
    }

    // 円の共通接線
    std::vector<Line> tangent(const Circle &a, const Circle &b) {
        std::vector<Line> ret;
        // 2円の中心間の距離
        D g = std::abs(a.p - b.p);
        // 円が内包されている場合
        if(equal(g, 0)) return ret;
        Point u = unitVector(b.p - a.p);
        Point v = rotate(u, PI / 2);
        for(int s : {-1, 1}) {
            D h = (a.r + b.r * s) / g;
            if(equal(h * h, 1)) {
                ret.emplace_back(a.p + (h > 0 ? u : -u) * a.r,
                                 a.p + (h > 0 ? u : -u) * a.r + v);

            } else if(1 - h * h > 0) {
                Point U = u * h, V = v * std::sqrt(1 - h * h);
                ret.emplace_back(a.p + (U + V) * a.r,
                                 b.p - (U + V) * (b.r * s));
                ret.emplace_back(a.p + (U - V) * a.r,
                                 b.p - (U - V) * (b.r * s));
            }
        }
        return ret;
    }

    // 多角形の面積を求める
    D PolygonArea(const std::vector<Point> &p) {
        D res = 0;
        int n = p.size();
        for(int i = 0; i < n - 1; i++) res += cross(p[i], p[i + 1]);
        res += cross(p[n - 1], p[0]);
        return res * 0.5;
    }

    // 凸多角形かどうか
    bool isConvex(const std::vector<Point> &p) {
        int n = p.size();
        int now, pre, nxt;
        for(int i = 0; i < n; i++) {
            pre = (i - 1 + n) % n;
            nxt = (i + 1) % n;
            now = i;
            if(ccw(p[pre], p[now], p[nxt]) == -1) return false;
        }
        return true;
    }

    // 凸包 O(NlogN)
    std::vector<Point> ConvexHull(std::vector<Point> p) {
        int n = (int)p.size(), k = 0;
        std::sort(p.begin(), p.end(), [](const Point &a, const Point &b) {
            return (a.real() != b.real() ? a.real() < b.real()
                                         : a.imag() < b.imag());
        });
        std::vector<Point> ch(2 * n);
        // 一直線上の3点を含める -> (< -EPS)
        // 含め無い -> (< EPS)
        for(int i = 0; i < n; ch[k++] = p[i++]) { // lower
            while(k >= 2 &&
                  cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < EPS)
                --k;
        }
        for(int i = n - 2, t = k + 1; i >= 0; ch[k++] = p[i--]) { // upper
            while(k >= t &&
                  cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < EPS)
                --k;
        }
        ch.resize(k - 1);
        return ch;
    }

    // 多角形gに点pが含まれているか?
    // 含まれる:2, 辺上にある:1, 含まれない:0
    int isContained(const std::vector<Point> &g, const Point &p) {
        bool in = false;
        int n = (int)g.size();
        for(int i = 0; i < n; i++) {
            Point a = g[i] - p, b = g[(i + 1) % n] - p;
            if(imag(a) > imag(b)) swap(a, b);
            if(imag(a) <= EPS && EPS < imag(b) && cross(a, b) < -EPS) in = !in;
            if(cross(a, b) == 0 && dot(a, b) <= 0) return 1;
        }
        return (in ? 2 : 0);
    }

    // 凸多角形pを直線lで切断し、その左側を返す
    std::vector<Point> ConvexCut(std::vector<Point> p, Line l) {
        std::vector<Point> ret;
        int sz = (int)p.size();
        for(int i = 0; i < sz; i++) {
            Point now = p[i];
            Point nxt = p[i == sz - 1 ? 0 : i + 1];
            if(ccw(l.a, l.b, now) != -1) ret.emplace_back(now);
            if(ccw(l.a, l.b, now) * ccw(l.a, l.b, nxt) < 0) {
                ret.emplace_back(crossPoint(Line(now, nxt), l));
            }
        }
        return ret;
    }

} // namespace geometry

void solve(ll n,ll m,ll l);

int main() {
    std::cin.tie(nullptr);
    std::ios::sync_with_stdio(false);
    std::cout << std::fixed << std::setprecision(20);
    while(1){
        ll t,p,r;
        cin>>t>>p>>r;
        if(t==0)return 0;
        solve(t,p,r);
    }
}

vecll dx = {1,0,-1,0};
vecll dy = {0,1,0,-1};

struct star{
    ll x,y,a,r;
    vector<geometry::Point> p;
    void f(){
        p.resize(5);
        rep(i,5){
            p[i] = geometry::Point(x-r*sin(geometry::degreeToRadian(a+72*i)),y+r*cos(geometry::degreeToRadian(a+72*i)));
        }
    }
};

double ds(star a,star b){
    double res=INF;
    rep(i,5)rep(j,5){
        geometry::Segment l1(a.p[i],a.p[(i+2)%5]);
        geometry::Segment l2(b.p[j],b.p[(j+2)%5]);
        chmin(res,geometry::distanceBetweenSegments(l1,l2));
    }
    return res;
}

void solve(ll n,ll m,ll l){
    m--;l--;
    vector<star> s(n);
    rep(i,n)cin>>s[i].x>>s[i].y>>s[i].a>>s[i].r;
    rep(i,n)s[i].f();
    vector<vecdou> di(n,vecdou(n,INF));
    rep(i,n)di[i][i]=0;
    rep(i,n)for(int j=i+1;j<n;j++){
        di[i][j]=ds(s[i],s[j]);
        di[j][i]=di[i][j];
    }
    vecdou dist(n,INF);
    dist[m]=0;
    priority_queue<pair<double, ll>, vector<pair<double, ll>>, greater<pair<double, ll>>> pq;
    pq.push({0,m});
    while(!pq.empty()){
        ll pos = pq.top().second; pq.pop();
        rep(to,n){
            if(to==pos)continue;
            double cost = di[pos][to];
            if (dist[to] > dist[pos] + cost) {
                dist[to] = dist[pos] + cost;
                pq.push(make_pair(dist[to], to));
            }
        }
    }
    cout<<dist[l]<<'\n';
}
accuracy: 1
time_ms: 80
memory_kb: 4224
final_rank: 4

code_snippet: #include <bits/stdc++.h>
using namespace std;
using ll=long long;
const ll ILL=2167167167167167167;
const int INF=2100000000;
#define rep(i,a,b) for (int i=(int)(a);i<(int)(b);i++)
#define all(p) p.begin(),p.end()
template<class T> using _pq = priority_queue<T, vector<T>, greater<T>>;
template<class T> int LB(vector<T> &v,T a){return lower_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> int UB(vector<T> &v,T a){return upper_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> bool chmin(T &a,T b){if(b<a){a=b;return 1;}else return 0;}
template<class T> bool chmax(T &a,T b){if(a<b){a=b;return 1;}else return 0;}
template<class T> void So(vector<T> &v) {sort(v.begin(),v.end());}
template<class T> void Sore(vector<T> &v) {sort(v.begin(),v.end(),[](T x,T y){return x>y;});}
bool yneos(bool a,bool upp=false){if(a){cout<<(upp?"YES\n":"Yes\n");}else{cout<<(upp?"NO\n":"No\n");}return a;}
template<class T> void vec_out(vector<T> &p,int ty=0){
    if(ty==2){cout<<'{';for(int i=0;i<(int)p.size();i++){if(i){cout<<",";}cout<<'"'<<p[i]<<'"';}cout<<"}\n";}
    else{if(ty==1){cout<<p.size()<<"\n";}for(int i=0;i<(int)(p.size());i++){if(i) cout<<" ";cout<<p[i];}cout<<"\n";}}
template<class T> T vec_min(vector<T> &a){assert(!a.empty());T ans=a[0];for(auto &x:a) chmin(ans,x);return ans;}
template<class T> T vec_max(vector<T> &a){assert(!a.empty());T ans=a[0];for(auto &x:a) chmax(ans,x);return ans;}
template<class T> T vec_sum(vector<T> &a){T ans=T(0);for(auto &x:a) ans+=x;return ans;}
int pop_count(long long a){int res=0;while(a){res+=(a&1),a>>=1;}return res;}
template<class T> T square(T a){return a * a;}
// https://github.com/kth-competitive-programming/kactl/tree/main/content/geometry
template<class T> ll sgn(T x) { return (x > 0) - (x < 0); }
template<class T> struct Point {
    using P = Point;
    T x, y;
    explicit Point(T x = 0, T y = 0) : x(x), y(y) {}
    bool operator<(P p) const { return tie(x, y) < tie(p.x, p.y); }
    bool operator==(P p) const { return tie(x, y) == tie(p.x, p.y); }
    P operator+(P p) const { return P(x + p.x, y + p.y); }
    P operator-(P p) const { return P(x - p.x, y - p.y); }
    P operator*(T d) const { return P(x * d, y * d); }
    P operator/(T d) const { return P(x / d, y / d); }
    T dot(P p) const { return x * p.x + y * p.y; }
    T cross(P p) const { return x * p.y - y * p.x; }
    T cross(P a, P b) const { return (a - *this).cross(b - *this); }
    T dist2() const { return x * x + y * y; }
    long double dist() const { return sqrtl(dist2()); }
    // angle to x-axis in interval [-pi, pi]
    double angle() const { return atan2(y, x); }
    P unit() const { return *this / dist(); }  // makes dist()=1
    P perp() const { return P(-y, x); }        // rotates +90 degrees
    P normal() const { return perp().unit(); }
    // returns point rotated 'a' radians ccw around the origin
    P rotate(double a) const {
        return P(x * cos(a) - y * sin(a), x * sin(a) + y * cos(a));
    }
    friend ostream& operator<<(ostream& os, P p) {
        return os << "(" << p.x << "," << p.y << ")";
    }
};

template<class P> bool onSegment(P s, P e, P p) {
    return p.cross(s, e) == 0 && (s - p).dot(e - p) <= 0;
}

template<class P> vector<P> segInter(P a, P b, P c, P d) {
    auto oa = c.cross(d, a), ob = c.cross(d, b),
            oc = a.cross(b, c), od = a.cross(b, d);
    // Checks if intersection is single non-endpoint point.
    if(sgn(oa) * sgn(ob) < 0 && sgn(oc) * sgn(od) < 0)
        return {(a * ob - b * oa) / (ob - oa)};
    set<P> s;
    if(onSegment(c, d, a)) s.insert(a);
    if(onSegment(c, d, b)) s.insert(b);
    if(onSegment(a, b, c)) s.insert(c);
    if(onSegment(a, b, d)) s.insert(d);
    return {all(s)};
}
using P = Point<long double>;
long double segDist(P& s, P& e, P& p) {
    if(s == e) return (p - s).dist();
    auto d = (e - s).dist2(), t = min(d, max((long double)(0), (p - s).dot(e - s)));
    return ((p - s) * d - (e - s) * t).dist() / d;
}


int N, M, L;

void solve();
// CITRUS CURIO CITY / FREDERIC
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    while (cin >> N >> M >> L, N) solve();
}

void solve(){
    using ld = long double;
    using P = Point<ld>;
    vector<vector<P>> pts(N);
    const ld pi = acos(-1);
    ld diff = (pi * 144) / 180;
    rep(i, 0, N){
        ld x, y, a, r;
        cin >> x >> y >> a >> r;
        a += 90;
        a /= 180;
        a *= pi;
        rep(j, 0, 6){
            ld X = x + r * cosl(a);
            ld Y = y + r * sinl(a);
            pts[i].push_back(Point<ld>{X, Y});
            a += diff;
            // cout << X << " " << Y << "\n";
        }
    }
    vector G(N, vector<ld>(N, INF));
    rep(i, 0, N) rep(j, 0, N){
        rep(k, 0, 5) rep(l, 0, 5){

            if (i == j){
                G[i][j] = 0;
                continue;
            }
            if (segInter(pts[i][k], pts[i][k + 1], pts[j][l], pts[j][l + 1]).empty()){
                rep(di, 0, 2) rep(dj, 0, 2){
                    chmin(G[i][j], (pts[i][k + di] - pts[j][l + dj]).dist());
                }
            }
            else{
                G[i][j] = 0;
            }
            ld tmp = segDist(pts[i][k], pts[i][k + 1], pts[j][l]);
            chmin(G[i][j], tmp);
            chmin(G[j][i], tmp);
        }
    }
    M--, L--;
    vector<ld> dist(N, INF);
    dist[M] = 0;
    vector<int> seen(N, 0);
    // rep(i, 0, N) vec_out(G[i]);
    rep(rp, 0, N){
        int ind = -1;
        rep(j, 0, N) if (seen[j] == 0){
            if (ind == -1 || dist[ind] > dist[j]) ind = j;
        }
        rep(i, 0, N) chmin(dist[i], dist[ind] + G[ind][i]);
        seen[ind] = 1;
    }
    cout << fixed << setprecision(20) << dist[L] << "\n";
}
accuracy: 1
time_ms: 240
memory_kb: 4216
final_rank: 7

code_snippet: #include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b) for (int i = (int)(a); i < (int)(b); i++)
#define rrep(i, a, b) for (int i = (int)(b)-1; i >= (int)(a); i--)
#define ALL(v) (v).begin(), (v).end()
#define UNIQUE(v) sort(ALL(v)), (v).erase(unique(ALL(v)), (v).end())
#define SZ(v) (int)v.size()
#define MIN(v) *min_element(ALL(v))
#define MAX(v) *max_element(ALL(v))
#define LB(v, x) int(lower_bound(ALL(v), (x)) - (v).begin())
#define UB(v, x) int(upper_bound(ALL(v), (x)) - (v).begin())

using uint = unsigned int;
using ll = long long int;
using ull = unsigned long long;
using i128 = __int128_t;
using u128 = __uint128_t;
const int inf = 0x3fffffff;
const ll INF = 0x1fffffffffffffff;

template <typename T> inline bool chmax(T &a, T b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T> inline bool chmin(T &a, T b) {
    if (a > b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T, typename U> T ceil(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? (x + y - 1) / y : x / y);
}
template <typename T, typename U> T floor(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? x / y : (x - y + 1) / y);
}
template <typename T> int popcnt(T x) {
    return __builtin_popcountll(x);
}
template <typename T> int topbit(T x) {
    return (x == 0 ? -1 : 63 - __builtin_clzll(x));
}
template <typename T> int lowbit(T x) {
    return (x == 0 ? -1 : __builtin_ctzll(x));
}

template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << "P(" << p.first << ", " << p.second << ")";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) {
    os << "{";
    for (int i = 0; i < vec.size(); i++) {
        os << vec[i] << (i + 1 == vec.size() ? "" : ", ");
    }
    os << "}";
    return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &map_var) {
    os << "{";
    for (auto itr = map_var.begin(); itr != map_var.end(); itr++) {
        os << "(" << itr->first << ", " << itr->second << ")";
        itr++;
        if (itr != map_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &set_var) {
    os << "{";
    for (auto itr = set_var.begin(); itr != set_var.end(); itr++) {
        os << *itr;
        ++itr;
        if (itr != set_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
#ifdef LOCAL
#define show(...) _show(0, #__VA_ARGS__, __VA_ARGS__)
#else
#define show(...) true
#endif
template <typename T> void _show(int i, T name) {
    cerr << '\n';
}
template <typename T1, typename T2, typename... T3>
void _show(int i, const T1 &a, const T2 &b, const T3 &...c) {
    for (; a[i] != ',' && a[i] != '\0'; i++)
        cerr << a[i];
    cerr << ":" << b << " ";
    _show(i + 1, a, c...);
}

#include <unistd.h>

namespace fastio {
static constexpr uint32_t SZ = 1 << 17;
char ibuf[SZ];
char obuf[SZ];
char out[100];
// pointer of ibuf, obuf


uint32_t pil = 0, pir = 0, por = 0;

struct Pre {
    char num[10000][4];
    constexpr Pre() : num() {
        for (int i = 0; i < 10000; i++) {
            int n = i;
            for (int j = 3; j >= 0; j--) {
                num[i][j] = n % 10 | '0';
                n /= 10;
            }
        }
    }
} constexpr pre;

inline void load() {
    memmove(ibuf, ibuf + pil, pir - pil);
    pir = pir - pil + fread(ibuf + pir - pil, 1, SZ - pir + pil, stdin);
    pil = 0;
    if (pir < SZ)
        ibuf[pir++] = '\n';
}

inline void flush() {
    fwrite(obuf, 1, por, stdout);
    por = 0;
}

void rd(char &c) {
    do {
        if (pil + 1 > pir)
            load();
        c = ibuf[pil++];
    } while (isspace(c));
}

void rd(string &x) {
    x.clear();
    char c;
    do {
        if (pil + 1 > pir)
            load();
        c = ibuf[pil++];
    } while (isspace(c));
    do {
        x += c;
        if (pil == pir)
            load();
        c = ibuf[pil++];
    } while (!isspace(c));
}

template <typename T> void rd_real(T &x) {
    string s;
    rd(s);
    x = stod(s);
}

template <typename T> void rd_integer(T &x) {
    if (pil + 100 > pir)
        load();
    char c;
    do
        c = ibuf[pil++];
    while (c < '-');
    bool minus = 0;
    if constexpr (is_signed<T>::value || is_same_v<T, i128>) {
        if (c == '-') {
            minus = 1, c = ibuf[pil++];
        }
    }
    x = 0;
    while ('0' <= c) {
        x = x * 10 + (c & 15), c = ibuf[pil++];
    }
    if constexpr (is_signed<T>::value || is_same_v<T, i128>) {
        if (minus)
            x = -x;
    }
}

void rd(int &x) {
    rd_integer(x);
}
void rd(ll &x) {
    rd_integer(x);
}
void rd(i128 &x) {
    rd_integer(x);
}
void rd(uint &x) {
    rd_integer(x);
}
void rd(ull &x) {
    rd_integer(x);
}
void rd(u128 &x) {
    rd_integer(x);
}
void rd(double &x) {
    rd_real(x);
}
void rd(long double &x) {
    rd_real(x);
}

template <class T, class U> void rd(pair<T, U> &p) {
    return rd(p.first), rd(p.second);
}
template <size_t N = 0, typename T> void rd_tuple(T &t) {
    if constexpr (N < std::tuple_size<T>::value) {
        auto &x = std::get<N>(t);
        rd(x);
        rd_tuple<N + 1>(t);
    }
}
template <class... T> void rd(tuple<T...> &tpl) {
    rd_tuple(tpl);
}

template <size_t N = 0, typename T> void rd(array<T, N> &x) {
    for (auto &d : x)
        rd(d);
}
template <class T> void rd(vector<T> &x) {
    for (auto &d : x)
        rd(d);
}

void read() {}
template <class H, class... T> void read(H &h, T &...t) {
    rd(h), read(t...);
}

void wt(const char c) {
    if (por == SZ)
        flush();
    obuf[por++] = c;
}
void wt(const string s) {
    for (char c : s)
        wt(c);
}
void wt(const char *s) {
    size_t len = strlen(s);
    for (size_t i = 0; i < len; i++)
        wt(s[i]);
}

template <typename T> void wt_integer(T x) {
    if (por > SZ - 100)
        flush();
    if (x < 0) {
        obuf[por++] = '-', x = -x;
    }
    int outi;
    for (outi = 96; x >= 10000; outi -= 4) {
        memcpy(out + outi, pre.num[x % 10000], 4);
        x /= 10000;
    }
    if (x >= 1000) {
        memcpy(obuf + por, pre.num[x], 4);
        por += 4;
    } else if (x >= 100) {
        memcpy(obuf + por, pre.num[x] + 1, 3);
        por += 3;
    } else if (x >= 10) {
        int q = (x * 103) >> 10;
        obuf[por] = q | '0';
        obuf[por + 1] = (x - q * 10) | '0';
        por += 2;
    } else
        obuf[por++] = x | '0';
    memcpy(obuf + por, out + outi + 4, 96 - outi);
    por += 96 - outi;
}

template <typename T> void wt_real(T x) {
    ostringstream oss;
    oss << fixed << setprecision(15) << double(x);
    string s = oss.str();
    wt(s);
}

void wt(int x) {
    wt_integer(x);
}
void wt(ll x) {
    wt_integer(x);
}
void wt(i128 x) {
    wt_integer(x);
}
void wt(uint x) {
    wt_integer(x);
}
void wt(ull x) {
    wt_integer(x);
}
void wt(u128 x) {
    wt_integer(x);
}
void wt(double x) {
    wt_real(x);
}
void wt(long double x) {
    wt_real(x);
}

template <class T, class U> void wt(const pair<T, U> val) {
    wt(val.first);
    wt(' ');
    wt(val.second);
}
template <size_t N = 0, typename T> void wt_tuple(const T t) {
    if constexpr (N < std::tuple_size<T>::value) {
        if constexpr (N > 0) {
            wt(' ');
        }
        const auto x = std::get<N>(t);
        wt(x);
        wt_tuple<N + 1>(t);
    }
}
template <class... T> void wt(tuple<T...> tpl) {
    wt_tuple(tpl);
}
template <class T, size_t S> void wt(const array<T, S> val) {
    auto n = val.size();
    for (size_t i = 0; i < n; i++) {
        if (i)
            wt(' ');
        wt(val[i]);
    }
}
template <class T> void wt(const vector<T> val) {
    auto n = val.size();
    for (size_t i = 0; i < n; i++) {
        if (i)
            wt(' ');
        wt(val[i]);
    }
}

void print() {
    wt('\n');
}
template <class Head, class... Tail> void print(Head &&head, Tail &&...tail) {
    wt(head);
    if (sizeof...(Tail))
        wt(' ');
    print(forward<Tail>(tail)...);
}
void __attribute__((destructor)) _d() {
    flush();
}
} // namespace fastio


using fastio::flush;
using fastio::print;
using fastio::read;

inline void first(bool i = true) {
    print(i ? "first" : "second");
}
inline void Alice(bool i = true) {
    print(i ? "Alice" : "Bob");
}
inline void Takahashi(bool i = true) {
    print(i ? "Takahashi" : "Aoki");
}
inline void yes(bool i = true) {
    print(i ? "yes" : "no");
}
inline void Yes(bool i = true) {
    print(i ? "Yes" : "No");
}
inline void No() {
    print("No");
}
inline void YES(bool i = true) {
    print(i ? "YES" : "NO");
}
inline void NO() {
    print("NO");
}
inline void Yay(bool i = true) {
    print(i ? "Yay!" : ":(");
}
inline void Possible(bool i = true) {
    print(i ? "Possible" : "Impossible");
}
inline void POSSIBLE(bool i = true) {
    print(i ? "POSSIBLE" : "IMPOSSIBLE");
}

/**
 * @brief Fast IO
 */

using T = double;
const T eps = 1e-12;
using Point = complex<T>;
using Poly = vector<Point>;
#define X real()
#define Y imag()
template <typename T> inline bool eq(const T &a, const T &b) {
    return fabs(a - b) < eps;
}
bool cmp(const Point &a, const Point &b) {
    auto sub = [&](Point a) {
        return (a.Y < 0 ? -1 : (a.Y == 0 && a.X >= 0 ? 0 : 1));
    };
    if (sub(a) != sub(b))
        return sub(a) < sub(b);
    return a.Y * b.X < a.X * b.Y;
}
struct Line {
    Point a, b, dir;
    Line() {}
    Line(Point _a, Point _b) : a(_a), b(_b), dir(b - a) {}
    Line(T A, T B, T C) {
        if (eq(A, .0)) {
            a = Point(0, C / B), b = Point(1 / C / B);
        } else if (eq(B, .0)) {
            a = Point(C / A, 0), b = Point(C / A, 1);
        } else {
            a = Point(0, C / B), b = Point(C / A, 0);
        }
    }
};
struct Segment : Line {
    Segment() {}
    Segment(Point _a, Point _b) : Line(_a, _b) {}
};
struct Circle {
    Point p;
    T r;
    Circle() {}
    Circle(Point _p, T _r) : p(_p), r(_r) {}
};

istream &operator>>(istream &is, Point &p) {
    T x, y;
    is >> x >> y;
    p = Point(x, y);
    return is;
}
ostream &operator<<(ostream &os, Point &p) {
    os << fixed << setprecision(12) << p.X << ' ' << p.Y;
    return os;
}
Point unit(const Point &a) {
    return a / abs(a);
}
T dot(const Point &a, const Point &b) {
    return a.X * b.X + a.Y * b.Y;
}
T cross(const Point &a, const Point &b) {
    return a.X * b.Y - a.Y * b.X;
}
Point rot(const Point &a, const T &theta) {
    return Point(cos(theta) * a.X - sin(theta) * a.Y,
                 sin(theta) * a.X + cos(theta) * a.Y);
}
Point rot90(const Point &a) {
    return Point(-a.Y, a.X);
}
T arg(const Point &a, const Point &b, const Point &c) {
    double ret = acos(dot(a - b, c - b) / abs(a - b) / abs(c - b));
    if (cross(a - b, c - b) < 0)
        ret = -ret;
    return ret;
}

Point Projection(const Line &l, const Point &p) {
    T t = dot(p - l.a, l.a - l.b) / norm(l.a - l.b);
    return l.a + (l.a - l.b) * t;
}
Point Projection(const Segment &l, const Point &p) {
    T t = dot(p - l.a, l.a - l.b) / norm(l.a - l.b);
    return l.a + (l.a - l.b) * t;
}
Point Reflection(const Line &l, const Point &p) {
    return p + (Projection(l, p) - p) * 2.;
}
int ccw(const Point &a, Point b, Point c) {
    b -= a;
    c -= a;
    if (cross(b, c) > eps)
        return 1; // ccw

    if (cross(b, c) < -eps)
        return -1; // cw

    if (dot(b, c) < 0)
        return 2; // c,a,b

    if (norm(b) < norm(c))
        return -2; // a,b,c

    return 0;      // a,c,b

}
bool isOrthogonal(const Line &a, const Line &b) {
    return eq(dot(a.b - a.a, b.b - b.a), .0);
}
bool isParallel(const Line &a, const Line &b) {
    return eq(cross(a.b - a.a, b.b - b.a), .0);
}
bool isIntersect(const Segment &a, const Segment &b) {
    return ccw(a.a, a.b, b.a) * ccw(a.a, a.b, b.b) <= 0 and
           ccw(b.a, b.b, a.a) * ccw(b.a, b.b, a.b) <= 0;
}
int isIntersect(const Circle &a, const Circle &b) {
    T d = abs(a.p - b.p);
    if (d > a.r + b.r + eps)
        return 4;
    if (eq(d, a.r + b.r))
        return 3;
    if (eq(d, abs(a.r - b.r)))
        return 1;
    if (d < abs(a.r - b.r) - eps)
        return 0;
    return 2;
}
T Dist(const Line &a, const Point &b) {
    Point c = Projection(a, b);
    return abs(c - b);
}
T Dist(const Segment &a, const Point &b) {
    if (dot(a.b - a.a, b - a.a) < eps)
        return abs(b - a.a);
    if (dot(a.a - a.b, b - a.b) < eps)
        return abs(b - a.b);
    return abs(cross(a.b - a.a, b - a.a)) / abs(a.b - a.a);
}
T Dist(const Segment &a, const Segment &b) {
    if (isIntersect(a, b))
        return .0;
    T res = min({Dist(a, b.a), Dist(a, b.b), Dist(b, a.a), Dist(b, a.b)});
    return res;
}
Point Intersection(const Line &a, const Line &b) {
    T d1 = cross(a.b - a.a, b.b - b.a);
    T d2 = cross(a.b - a.a, a.b - b.a);
    if (eq(d1, 0.) and eq(d2, 0.))
        return b.a;
    return b.a + (b.b - b.a) * (d2 / d1);
}
Poly Intersection(const Circle &a, const Line &b) {
    Poly res;
    T d = Dist(b, a.p);
    if (d > a.r + eps)
        return res;
    Point h = Projection(b, a.p);
    if (eq(d, a.r)) {
        res.push_back(h);
        return res;
    }
    Point e = unit(b.b - b.a);
    T ph = sqrt(a.r * a.r - d * d);
    res.push_back(h - e * ph);
    res.push_back(h + e * ph);
    return res;
}
Poly Intersection(const Circle &a, const Segment &b) {
    Line c(b.a, b.b);
    Poly sub = Intersection(a, c);
    double xmi = min(b.a.X, b.b.X), xma = max(b.a.X, b.b.X);
    double ymi = min(b.a.Y, b.b.Y), yma = max(b.a.Y, b.b.Y);
    Poly res;
    rep(i, 0, sub.size()) {
        if (xmi <= sub[i].X + eps and sub[i].X - eps <= xma and
            ymi <= sub[i].Y + eps and sub[i].Y - eps <= yma) {
            res.push_back(sub[i]);
        }
    }
    return res;
}
Poly Intersection(const Circle &a, const Circle &b) {
    Poly res;
    int mode = isIntersect(a, b);
    T d = abs(a.p - b.p);
    if (mode == 4 or mode == 0)
        return res;
    if (mode == 3) {
        T t = a.r / (a.r + b.r);
        res.push_back(a.p + (b.p - a.p) * t);
        return res;
    }
    if (mode == 1) {
        if (b.r < a.r - eps) {
            res.push_back(a.p + (b.p - a.p) * (a.r / d));
        } else {
            res.push_back(b.p + (a.p - b.p) * (b.r / d));
        }
        return res;
    }
    T rc = (a.r * a.r + d * d - b.r * b.r) / d / 2.;
    T rs = sqrt(a.r * a.r - rc * rc);
    if (a.r - abs(rc) < eps)
        rs = 0;
    Point e = unit(b.p - a.p);
    res.push_back(a.p + rc * e + rs * e * Point(0, 1));
    res.push_back(a.p + rc * e + rs * e * Point(0, -1));
    return res;
}
Poly HalfplaneIntersection(vector<Line> &H) {
    sort(ALL(H), [&](Line &l1, Line &l2) { return cmp(l1.dir, l2.dir); });
    auto outside = [&](Line &L, Point p) -> bool {
        return cross(L.dir, p - L.a) < -eps;
    };
    deque<Line> deq;
    int sz = 0;
    rep(i, 0, SZ(H)) {
        while (sz > 1 and
               outside(H[i], Intersection(deq[sz - 1], deq[sz - 2]))) {
            deq.pop_back();
            sz--;
        }
        while (sz > 1 and outside(H[i], Intersection(deq[0], deq[1]))) {
            deq.pop_front();
            sz--;
        }
        if (sz > 0 and fabs(cross(H[i].dir, deq[sz - 1].dir)) < eps) {
            if (dot(H[i].dir, deq[sz - 1].dir) < 0) {
                return {};
            }
            if (outside(H[i], deq[sz - 1].a)) {
                deq.pop_back();
                sz--;
            } else
                continue;
        }
        deq.push_back(H[i]);
        sz++;
    }

    while (sz > 2 and outside(deq[0], Intersection(deq[sz - 1], deq[sz - 2]))) {
        deq.pop_back();
        sz--;
    }
    while (sz > 2 and outside(deq[sz - 1], Intersection(deq[0], deq[1]))) {
        deq.pop_front();
        sz--;
    }
    if (sz < 3)
        return {};
    deq.push_back(deq.front());
    Poly ret;
    rep(i, 0, sz) ret.push_back(Intersection(deq[i], deq[i + 1]));
    return ret;
}

T Area(const Poly &a) {
    T res = 0;
    int n = a.size();
    rep(i, 0, n) res += cross(a[i], a[(i + 1) % n]);
    return fabs(res / 2.);
}
T Area(const Poly &a, const Circle &b) {
    int n = a.size();
    if (n < 3)
        return .0;
    auto rec = [&](auto self, const Circle &c, const Point &p1,
                   const Point &p2) {
        Point va = c.p - p1, vb = c.p - p2;
        T f = cross(va, vb), res = .0;
        if (eq(f, .0))
            return res;
        if (max(abs(va), abs(vb)) < c.r + eps)
            return f;
        if (Dist(Segment(p1, p2), c.p) > c.r - eps)
            return c.r * c.r * arg(vb * conj(va));
        auto u = Intersection(c, Segment(p1, p2));
        Poly sub;
        sub.push_back(p1);
        for (auto &x : u)
            sub.push_back(x);
        sub.push_back(p2);
        rep(i, 0, sub.size() - 1) res += self(self, c, sub[i], sub[i + 1]);
        return res;
    };
    T res = .0;
    rep(i, 0, n) res += rec(rec, b, a[i], a[(i + 1) % n]);
    return fabs(res / 2.);
}
T Area(const Circle &a, const Circle &b) {
    T d = abs(a.p - b.p);
    if (d >= a.r + b.r - eps)
        return .0;
    if (d <= abs(a.r - b.r) + eps) {
        T r = min(a.r, b.r);
        return M_PI * r * r;
    }
    T ath = acos((a.r * a.r + d * d - b.r * b.r) / d / a.r / 2.);
    T res = a.r * a.r * (ath - sin(ath * 2) / 2.);
    T bth = acos((b.r * b.r + d * d - a.r * a.r) / d / b.r / 2.);
    res += b.r * b.r * (bth - sin(bth * 2) / 2.);
    return fabs(res);
}
bool isConvex(const Poly &a) {
    int n = a.size();
    int cur, pre, nxt;
    rep(i, 0, n) {
        pre = (i - 1 + n) % n;
        nxt = (i + 1) % n;
        cur = i;
        if (ccw(a[pre], a[cur], a[nxt]) == -1)
            return 0;
    }
    return 1;
}
int isContained(const Poly &a,
                const Point &b) { // 0:not contain,1:on edge,2:contain

    bool res = 0;
    int n = a.size();
    rep(i, 0, n) {
        Point p = a[i] - b, q = a[(i + 1) % n] - b;
        if (p.Y > q.Y)
            swap(p, q);
        if (p.Y < eps and eps < q.Y and cross(p, q) > eps)
            res ^= 1;
        if (eq(cross(p, q), .0) and dot(p, q) < eps)
            return 1;
    }
    return (res ? 2 : 0);
}
Poly ConvexHull(Poly &a) {
    sort(ALL(a), [](const Point &p, const Point &q) {
        return (eq(p.Y, q.Y) ? p.X < q.X : p.Y < q.Y);
    });
    a.erase(unique(ALL(a)), a.end());
    int n = a.size(), k = 0;
    Poly res(n * 2);
    for (int i = 0; i < n; res[k++] = a[i++]) {
        while (k >= 2 and
               cross(res[k - 1] - res[k - 2], a[i] - res[k - 1]) < eps)
            k--;
    }
    for (int i = n - 2, t = k + 1; i >= 0; res[k++] = a[i--]) {
        while (k >= t and
               cross(res[k - 1] - res[k - 2], a[i] - res[k - 1]) < eps)
            k--;
    }
    res.resize(k - 1);
    return res;
}
T Diam(const Poly &a) {
    int n = a.size();
    int x = 0, y = 0;
    rep(i, 1, n) {
        if (a[i].Y > a[x].Y)
            x = i;
        if (a[i].Y < a[y].Y)
            y = i;
    }
    T res = abs(a[x] - a[y]);
    int i = x, j = y;
    do {
        if (cross(a[(i + 1) % n] - a[i], a[(j + 1) % n] - a[j]) < 0)
            i = (i + 1) % n;
        else
            j = (j + 1) % n;
        chmax(res, abs(a[i] - a[j]));
    } while (i != x or j != y);
    return res;
}
Poly Cut(const Poly &a, const Line &l) {
    int n = a.size();
    Poly res;
    rep(i, 0, n) {
        Point p = a[i], q = a[(i + 1) % n];
        if (ccw(l.a, l.b, p) != -1)
            res.push_back(p);
        if (ccw(l.a, l.b, p) * ccw(l.a, l.b, q) < 0)
            res.push_back(Intersection(Line(p, q), l));
    }
    return res;
}

T Closest(Poly &a) {
    int n = a.size();
    if (n <= 1)
        return 0;
    sort(ALL(a), [&](Point a, Point b) {
        return (eq(a.X, b.X) ? a.Y < b.Y : a.X < b.X);
    });
    Poly buf(n);
    auto rec = [&](auto self, int lb, int rb) -> T {
        if (rb - lb <= 1)
            return (T)INF;
        int mid = (lb + rb) >> 1;
        auto x = a[mid].X;
        T res = min(self(self, lb, mid), self(self, mid, rb));
        inplace_merge(a.begin() + lb, a.begin() + mid, a.begin() + rb,
                      [&](auto p, auto q) { return p.Y < q.Y; });
        int ptr = 0;
        rep(i, lb, rb) {
            if (abs(a[i].X - x) >= res)
                continue;
            rep(j, 0, ptr) {
                auto sub = a[i] - buf[ptr - 1 - j];
                if (sub.Y >= res)
                    break;
                chmin(res, abs(sub));
            }
            buf[ptr++] = a[i];
        }
        return res;
    };
    return rec(rec, 0, n);
}

Circle Incircle(const Point &a, const Point &b, const Point &c) {
    T A = abs(b - c), B = abs(c - a), C = abs(a - b);
    Point p(A * a.X + B * b.X + C * c.X, A * a.Y + B * b.Y + C * c.Y);
    p /= (A + B + C);
    T r = Dist(Line(a, b), p);
    return Circle(p, r);
}
Circle Circumcircle(const Point &a, const Point &b, const Point &c) {
    Line l1((a + b) / 2., (a + b) / 2. + (b - a) * Point(0, 1));
    Line l2((b + c) / 2., (b + c) / 2. + (c - b) * Point(0, 1));
    Point p = Intersection(l1, l2);
    return Circle(p, abs(p - a));
}
Poly tangent(const Point &a, const Circle &b) {
    return Intersection(b, Circle(a, sqrt(norm(b.p - a) - b.r * b.r)));
}
vector<Line> tangent(const Circle &a, const Circle &b) {
    vector<Line> res;
    T d = abs(a.p - b.p);
    if (eq(d, 0.))
        return res;
    Point u = unit(b.p - a.p);
    Point v = u * Point(0, 1);
    for (int t : {-1, 1}) {
        T h = (a.r + b.r * t) / d;
        if (eq(h * h, 1.)) {
            res.push_back(Line(a.p + (h > 0 ? u : -u) * a.r,
                               a.p + (h > 0 ? u : -u) * a.r + v));
        } else if (1 > h * h) {
            Point U = u * h, V = v * sqrt(1 - h * h);
            res.push_back(Line(a.p + (U + V) * a.r, b.p - (U + V) * (b.r * t)));
            res.push_back(Line(a.p + (U - V) * a.r, b.p - (U - V) * (b.r * t)));
        }
    }
    return res;
}

/**
 * @brief Geometry
 */

double PI = 3.14159265358979;

int main() {
while(1) {
    int N, S, G;
    cin >> N >> S >> G;
    if (N == 0) return 0;
    S--, G--;
    vector<vector<Segment>> Seg(N);
    rep(i,0,N) {
        double PX, PY, A, R;
        cin >> PY >> PX >> A >> R;
        A = 72 - A;
        Point p(PX,PY);
        vector<Point> PS;
        rep(j,0,5) {
            double Arg = PI * (j * 144.0 + A) / 180.0;
            PS.push_back(p + polar(R,Arg));
        }
        rep(j,0,5) {
            Seg[i].push_back(Segment(PS[j],PS[(j+1)%5]));
        }
    }
    vector<vector<double>> D(N, vector<double>(N,inf));
    rep(i,0,N) {
        rep(j,0,N) {
            rep(k,0,5) {
                rep(l,0,5) {
                    chmin(D[i][j], Dist(Seg[i][k], Seg[j][l]));
                }
            }
        }
    }
    vector<double> DP(N,inf);
    priority_queue<pair<double,int>,vector<pair<double,int>>,greater<pair<double,int>>> PQ;
    DP[S] = 0;
    PQ.push({0,S});
    while(!PQ.empty()) {
        auto [A,B] = PQ.top();
        PQ.pop();
        if (DP[B] < A) continue;
        rep(i,0,N) {
            if (i == B) continue;
            double NA = A + D[B][i];
            if (chmin(DP[i],NA)) {
                PQ.push({NA,i});
            }
        }
    }
    printf("%.12lf\n", DP[G]);
}
}
accuracy: 1
time_ms: 210
memory_kb: 4168
final_rank: 6

code_snippet: #include <bits/stdc++.h>
//#include <atcoder/all>
using ll = long long;
#define MOD 1000000007
#define Mod 998244353
const int MAX = 1000000005;
const long long INF = 1000000000000000005LL;
using namespace std;
//using namespace atcoder;


const double EPS = 1e-8;

struct Point {
	double x, y;
};
Point operator+(const Point& a, const Point& b) {
    return {a.x + b.x, a.y + b.y};
}
Point operator-(const Point& a, const Point& b) {
    return {a.x - b.x, a.y - b.y};
}
Point operator*(const Point& p, const double& a) {
    return {p.x * a, p.y * a};
}

double norm(Point P) {return sqrt(P.x*P.x + P.y*P.y);}
double arg(Point P) {return atan2(P.y, P.x);}

double dot(Point P, Point Q) {
	return P.x*Q.x + P.y*Q.y;
}

double cross(Point P, Point Q) {
	return P.x*Q.y - P.y*Q.x;
}

int ccw(Point a, Point b, Point c) {
	if (cross(b - a, c - a) > EPS) return 1;
	if (cross(b - a, c - a) < -EPS) return -1;
	if (dot(b - a, c - a) < -EPS) return 2;
	if (norm(b - a) + EPS < norm(c - a)) return -2;
	return 0;
}

struct Segment {
	Point a, b;
};

bool intersect(Segment s1, Segment s2) {
    bool b1 = (ccw(s1.a, s1.b, s2.a)*ccw(s1.a, s1.b, s2.b) <= 0);
    bool b2 = (ccw(s2.a, s2.b, s1.a)*ccw(s2.a, s2.b, s1.b) <= 0);
    return b1 && b2;
}

Point cross_point(Segment s1, Segment s2) {
	Point a = s1.a, b = s1.b, c = s2.a, d = s2.b;
	Point p;
	p.x = a.x + cross(a-c, d-c)/cross(d-c, b-a) * (b-a).x;
	p.y = a.y + cross(a-c, d-c)/cross(d-c, b-a) * (b-a).y;
	return p;
}

Point projection(Point p1, Point p2, Point p) {
    return p1 + (p2 - p1) * (dot(p - p1, p2 - p1)/pow(norm(p2 - p1), 2));
}

bool on_line(Segment s, Point p) {
    double norm1 = norm(p - s.a), norm2 = norm(p - s.b), norm3 = norm(s.b - s.a);
    return (norm1 + EPS < norm3 && norm2 + EPS < norm3);
}

double distance(Segment s, Point p) {
    Point q = projection(s.a, s.b, p);
    if (on_line(s, q)) return norm(p - q);
    else return min(norm(s.a - p), norm(s.b - p));
}

double distance(Segment s1, Segment s2) {
    if (intersect(s1, s2)) return 0;
    return min({
        distance(s1, s2.a), distance(s1, s2.b), distance(s2, s1.a), distance(s2, s1.b)
    });
}

using d_i = pair<double, int>;


bool solve() {
    int N, M, L;
    cin >> N >> M >> L;
    if (N == 0 && M == 0 && L == 0) return false;
    M--; L--;
    vector<int> X(N), Y(N), A(N), R(N);
    for (int i = 0; i < N; i++) cin >> X[i] >> Y[i] >> A[i] >> R[i];
    vector<vector<double>> cost(N, vector<double>(N, 1e+10));
    for (int i = 0; i < N-1; i++) {
        for (int j = i+1; j < N; j++) {
            for (int k = 0; k < 5; k++) {
                for (int l = 0; l < 5; l++) {
                    vector<Point> P(4);
                    vector<double> angle(4);
                    angle[0] = (double)(A[i] + 72*k)/180*M_PI; angle[1] = (double)(A[i] + 72*(k + 2))/180*M_PI;
                    angle[2] = (double)(A[j] + 72*l)/180*M_PI; angle[3] = (double)(A[j] + 72*(l + 2))/180*M_PI;
                    for (int idx = 0; idx < 2; idx++) P[idx] = {X[i] - R[i]*sin(angle[idx]), Y[i] + R[i]*cos(angle[idx])};
                    for (int idx = 0; idx < 2; idx++) P[idx+2] = {X[j] - R[j]*sin(angle[idx+2]), Y[j] + R[j]*cos(angle[idx+2])};
                    Segment s1 = {P[0], P[1]}, s2 = {P[2], P[3]};
                    cost[i][j] = min(cost[i][j], distance(s1, s2));
                    cost[j][i] = min(cost[j][i], distance(s1, s2));
                }
            }
        }
    }
    priority_queue<d_i, vector<d_i>, greater<d_i>> pq;
    vector<double> dist(N, 1e+10);
    pq.push({0, M});
    dist[M] = 0;
    while (pq.size()) {
        auto [d, v] = pq.top();
        pq.pop();
        if (d > dist[v] + EPS) continue;
        for (int nv = 0; nv < N; nv++) {
            if (nv == v) continue;
            if (d + cost[v][nv] > dist[nv] - EPS) continue;
            dist[nv] = d + cost[v][nv];
            pq.push({dist[nv], nv});
        }
    }
    cout << fixed << setprecision(12) << dist[L] << endl;
    return true;
}

int main() {
    ios::sync_with_stdio(0); cin.tie();
    while (solve()) {}
}
accuracy: 1
time_ms: 490
memory_kb: 4036
final_rank: 13

code_snippet: #include <bits/stdc++.h>

#include <algorithm>
#include <cmath>
#include <iterator>
#include <utility>
#include <vector>

using Real = long double;
constexpr Real EPS = 1e-10;
const Real PI = std::acos(-1);

int sign(Real x) {
    return x < -EPS ? -1 : x > EPS ? 1 : 0;
}

bool eq(Real lhs, Real rhs) {
    return sign(rhs - lhs) == 0;
}

bool lte(Real lhs, Real rhs) {
    return sign(rhs - lhs) >= 0;
}

bool lt(Real lhs, Real rhs) {
    return sign(rhs - lhs) > 0;
}

bool gte(Real lhs, Real rhs) {
    return lte(rhs, lhs);
}

bool gt(Real lhs, Real rhs) {
    return lt(rhs, lhs);
}

struct Point {
    Real x;
    Real y;
    Point(Real x = 0, Real y = 0): x(x), y(y) {}
    Point& operator+=(Point rhs) {
        this->x += rhs.x;
        this->y += rhs.y;
        return *this;
    }
    Point& operator-=(Point rhs) {
        this->x -= rhs.x;
        this->y -= rhs.y;
        return *this;
    }
    Point& operator*=(Real k) {
        this->x *= k;
        this->y *= k;
        return *this;
    }
    Point& operator/=(Real k) {
        this->x /= k;
        this->y /= k;
        return *this;
    }
    friend Point operator+(Point lhs, Point rhs) {
        return lhs += rhs;
    }
    friend Point operator-(Point lhs, Point rhs) {
        return lhs -= rhs;
    }
    friend Point operator*(Point p, Real k) {
        return p *= k;
    }
    friend Point operator/(Point p, Real k) {
        return p /= k;
    }
    friend Point operator*(Real k, Point p) {
        return p * k;
    }
    friend Point operator/(Real k, Point p) {
        return p / k;
    }
    friend bool operator==(Point lhs, Point rhs) {
        return eq(lhs.x, rhs.x) && eq(lhs.y, rhs.y);
    }
    friend bool operator<(Point lhs, Point rhs) {
        if (eq(lhs.x, rhs.x)) return lt(lhs.y, rhs.y);
        return lt(lhs.x, rhs.x);
    }
    friend bool operator>(Point lhs, Point rhs) {
        return rhs < lhs;
    }
};

using Points = std::vector<Point>;
using Polygon = Points;
using Vector = Point;

Real norm(Vector p) {
    return p.x * p.x + p.y * p.y;
}

Real abs(Vector p) {
    return std::sqrt(norm(p));
}

Real dot(Vector lhs, Vector rhs) {
    return lhs.x * rhs.x + lhs.y * rhs.y;
}

Real cross(Vector lhs, Vector rhs) {
    return lhs.x * rhs.y - lhs.y * rhs.x;
}

int ccw(Point p0, Point p1, Point p2) {
    Vector a = p1 - p0;
    Vector b = p2 - p0;

    if (cross(a, b) > EPS) return 1;

    if (cross(a, b) < -EPS) return -1;

    if (dot(a, b) < 0) return 2;

    if (norm(a) < norm(b)) return -2;

    return 0;
}

struct Circle {
    Real r;
    Point c;
    Circle() {}
    Circle(Real r, Point c): r(r), c(c) {}
};

struct Segment {
    Segment() {}
    Segment(Point p0, Point p1): p0(p0), p1(p1) {}
    Point p0, p1;
};

struct Line {
    Line() {}
    Line(Point p0, Point p1): p0(p0), p1(p1) {}
    explicit Line(Segment s): p0(s.p0), p1(s.p1) {}
    Point p0, p1;
};

Real distance(Point lhs, Point rhs) {
    return abs(rhs - lhs);
}

Real distance(Line l, Point p) {
    return std::abs(cross(l.p1 - l.p0, p - l.p0)) / abs(l.p1 - l.p0);
}

Real distance(Segment s, Point p) {
    if (dot(s.p1 - s.p0, p - s.p0) < 0) {
        return distance(p, s.p0);
    }
    if (dot(s.p0 - s.p1, p - s.p1) < 0) {
        return distance(p, s.p1);
    }
    return distance(Line(s), p);
}

bool intersect(Segment lhs, Segment rhs) {
    return ccw(lhs.p0, lhs.p1, rhs.p0) * ccw(lhs.p0, lhs.p1, rhs.p1) <= 0
           && ccw(rhs.p0, rhs.p1, lhs.p0) * ccw(rhs.p0, rhs.p1, lhs.p1) <= 0;
}

bool intersect(Segment s, Point p) {
    return ccw(s.p0, s.p1, p) == 0;
}

Real distance(Segment lhs, Segment rhs) {
    if (intersect(lhs, rhs)) {
        return Real(0);
    }
    return std::min({distance(lhs, rhs.p0), distance(lhs, rhs.p1), distance(rhs, lhs.p0), distance(rhs, lhs.p1)});
}

bool parallel(Vector lhs, Vector rhs) {
    return eq(cross(lhs, rhs), 0);
}

bool parallel(Segment lhs, Segment rhs) {
    return parallel(lhs.p0 - lhs.p1, rhs.p0 - rhs.p1);
}

bool parallel(Line lhs, Line rhs) {
    return parallel(lhs.p0 - lhs.p1, rhs.p0 - rhs.p1);
}

Point crosspoint(Line lhs, Line rhs) {
    Real a = cross(lhs.p1 - lhs.p0, rhs.p1 - rhs.p0);
    Real b = cross(lhs.p1 - lhs.p0, lhs.p1 - rhs.p0);
    return rhs.p0 + (rhs.p1 - rhs.p0) * b / a;
}

bool intersect(Line l, Segment s) {
    return ccw(l.p0, l.p1, s.p0) * ccw(l.p0, l.p1, s.p1) <= 0;
}

bool intersect(Circle c, Line l) {
    return lte(distance(l, c.c), c.r);
}

bool intersect(Circle c, Point p) {
    return eq(distance(c.c, p), c.r);
}

int intersect(Circle lhs, Circle rhs) {
    if (gt(lhs.r, rhs.r)) std::swap(lhs, rhs);
    Real d = distance(lhs.c, rhs.c);
    if (lt(lhs.r + rhs.r, d)) return 4;
    if (eq(lhs.r + rhs.r, d)) return 3;
    if (gt(lhs.r + d, rhs.r)) return 2;
    if (eq(lhs.r + d, rhs.r)) return 1;
    return 0;
}

bool orthogonal(Vector lhs, Vector rhs) {
    return eq(dot(lhs, rhs), 0);
}

bool orthogonal(Segment lhs, Segment rhs) {
    return orthogonal(lhs.p0 - lhs.p1, rhs.p0 - rhs.p1);
}

bool orthogonal(Line lhs, Line rhs) {
    return orthogonal(lhs.p0 - lhs.p1, rhs.p0 - rhs.p1);
}

Point crosspoint(Segment lhs, Segment rhs) {
    Real d0 = distance(Line(lhs.p0, lhs.p1), rhs.p0);
    Real d1 = distance(Line(lhs.p0, lhs.p1), rhs.p1);
    return rhs.p0 + (rhs.p1 - rhs.p0) * (d0 / (d0 + d1));
}

Real arg(Vector p) {
    return std::atan2(p.y, p.x);
}

Vector polar(Real a, Real r) {
    return Point(std::cos(r) * a, std::sin(r) * a);
}

Point rotate(Point p, Real t) {
    Point v = polar(1, t);
    return Point(p.x * v.x - p.y * v.y, p.x * v.y + p.y * v.x);
}

Real angle(Point p0, Point p1, Point p2) {
    Real a = arg(p0 - p1);
    Real b = arg(p2 - p1);
    if (gt(a, b)) std::swap(a, b);
    return std::min(b - a, 2 * PI - (b - a));
}

Points crosspoint(Circle lhs, Circle rhs) {
    Real d = abs(lhs.c - rhs.c);
    if (eq(d, lhs.r + rhs.r)) return {lhs.c + lhs.r * (rhs.c - lhs.c) / d};
    Real a = std::acos((lhs.r * lhs.r + d * d - rhs.r * rhs.r) / (2 * lhs.r * d));
    Real t = arg(rhs.c - lhs.c);
    return {lhs.c + polar(lhs.r, t + a), lhs.c + polar(lhs.r, t - a)};
}

Point projection(Segment s, Point p) {
    Vector a = p - s.p0;
    Vector b = s.p1 - s.p0;
    Real t = dot(a, b) / norm(b);
    return s.p0 + t * b;
}

Point projection(Line l, Point p) {
    Vector a = p - l.p0;
    Vector b = l.p1 - l.p0;
    Real t = dot(a, b) / norm(b);
    return l.p0 + t * b;
}

Point reflection(Segment s, Point p) {
    return p + (projection(s, p) - p) * Real(2);
}

Point reflection(Line l, Point p) {
    return p + (projection(l, p) - p) * Real(2);
}

Points crosspoint(Circle c, Line l) {
    Vector p = projection(l, c.c);
    Real t = std::sqrt(c.r * c.r - norm(c.c - p));
    if (eq(t, 0)) return {p};
    Vector e = (l.p1 - l.p0) / abs(l.p1 - l.p0);
    return {p + t * e, p + -t * e};
}

Real area(Polygon poly) {
    int n = poly.size();
    Real result = 0;
    for (int i = 0; i < n; i++) {
        result += cross(poly[i], poly[(i + 1) % n]);
    }
    return result / 2;
}

Real perimeter(Polygon poly) {
    int n = poly.size();
    Real result = 0;
    for (int i = 0; i < n; i++) {
        result += abs(poly[i] - poly[(i + 1) % n]);
    }
    return result;
}

bool convex(Polygon poly) {
    int n = poly.size();
    for (int i = 0; i < n; i++) {
        if (ccw(poly[i], poly[(i + 1) % n], poly[(i + 2) % n]) == -1) return false;
    }
    return true;
}

int contain(Polygon poly, Point p) {
    int n = poly.size();
    bool parity = false;
    for (int i = 0; i < n; i++) {
        Point a = poly[i] - p;
        Point b = poly[(i + 1) % n] - p;
        if (gt(a.y, b.y)) std::swap(a, b);
        if (lte(a.y, 0) && lt(0, b.y) && lt(cross(a, b), 0)) parity ^= true;
        if (eq(cross(a, b), 0) && lte(dot(a, b), 0)) return 1;
    }
    return (parity ? 2 : 0);
}

Polygon convex_hull(Points points) {
    std::sort(points.begin(), points.end(), [](Point lhs, Point rhs) {
        if (eq(lhs.x, rhs.x)) return lt(lhs.y, rhs.y);
        return lt(lhs.x, rhs.x);
    });
    int n = points.size();
    Points lower, upper;
    lower.push_back(points[n - 1]);
    lower.push_back(points[n - 2]);
    upper.push_back(points[0]);
    upper.push_back(points[1]);
    for (int i = n - 3; i >= 0; i--) {
        for (int m = lower.size(); m >= 2 && ccw(lower[m - 2], lower[m - 1], points[i]) >= 0; m--) {
            lower.pop_back();
        }
        lower.push_back(points[i]);
    }
    for (int i = 2; i < n; i++) {
        for (int m = upper.size(); m >= 2 && ccw(upper[m - 2], upper[m - 1], points[i]) >= 0; m--) {
            upper.pop_back();
        }
        upper.push_back(points[i]);
    }
    std::reverse(lower.begin(), lower.end());
    std::copy(std::next(upper.rbegin()), std::prev(upper.rend()), std::back_inserter(lower));
    return lower;
}

Polygon convex_cut(Polygon poly, Line l) {
    int n = poly.size();
    Polygon res;
    for (int i = 0; i < n; i++) {
        Point a = poly[i];
        Point b = poly[(i + 1) % n];
        if (ccw(l.p0, l.p1, a) != -1) res.push_back(a);
        if (ccw(l.p0, l.p1, a) * ccw(l.p0, l.p1, b) == -1) {
            res.push_back(crosspoint(l, Line(a, b)));
        }
    }
    return res;
}

Line bisector(Point p0, Point p1, Point p2) {
    return Line(p1, p1 + polar(1, angle(p0, p1, p2) / 2));
}

Circle incircle(Point p0, Point p1, Point p2) {
    Point c = crosspoint(bisector(p0, p1, p2), bisector(p1, p2, p0));
    Real r = std::abs(2 * area({p0, p1, p2}) / perimeter({p0, p1, p2}));
    return Circle(r, c);
}

Line perpendicular(Line l, Point p) {
    Vector v = l.p1 - l.p0;
    return Line(p, p + Vector(-v.y, v.x));
}

Line perpendicular_bisector(Segment s) {
    return perpendicular(Line(s), (s.p0 + s.p1) / 2);
}

Circle circumscribed_circle(Point p0, Point p1, Point p2) {
    Point c = crosspoint(perpendicular_bisector(Segment(p0, p1)), perpendicular_bisector(Segment(p0, p2)));
    return Circle(distance(p0, c), c);
}

Real area(Circle c) {
    return c.r * c.r * PI;
}

Points tangent(Circle c, Point p) {
    return crosspoint(c, Circle(std::sqrt(norm(c.c - p) - c.r * c.r), p));
}

Real calc(std::vector<Segment> lhs, std::vector<Segment> rhs) {
    Real result = 1e18;
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            result = std::min(result, distance(lhs[i], rhs[j]));
        }
    }
    return result;
}

int solve() {
    int n, s, t;
    std::cin >> n >> s >> t;
    s--;
    t--;
    if (n == 0) return 1;
    using Segments = std::vector<Segment>;
    std::vector<Segments> stars;
    for (int i = 0; i < n; i++) {
        Point c;
        std::cin >> c.x >> c.y;
        int a, r;
        std::cin >> a >> r;
        Polygon poly;
        Point init(0, r);
        for (int j = 0; j < 360; j += 72) {
            Real t = (2 * PI) * (Real((j + a) % 360) / 360);
            poly.push_back(c + rotate(init, t));
        }
        Segments segments;
        segments.push_back(Segment(poly[0], poly[2]));
        segments.push_back(Segment(poly[0], poly[3]));
        segments.push_back(Segment(poly[1], poly[3]));
        segments.push_back(Segment(poly[1], poly[4]));
        segments.push_back(Segment(poly[2], poly[4]));
        stars.push_back(segments);
    }
    constexpr Real INF = 1e18;
    std::vector dist(n, std::vector<Real>(n, INF));
    for (int i = 0; i < n; i++) dist[i][i] = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            if (i == j) continue;
            dist[i][j] = calc(stars[i], stars[j]);
        }
    }
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dist[i][j] = std::min(dist[i][j], dist[i][k] + dist[k][j]);
            }
        }
    }
    std::cout << std::fixed << std::setprecision(10);
    std::cout << dist[s][t] << std::endl;
    return 0;
}

int main() {
    while (!solve())
        ;
}
accuracy: 1
time_ms: 350
memory_kb: 4124
final_rank: 8

code_snippet: #include <bits/stdc++.h>
using namespace std;

using ll = long long;
using vll = vector<ll>;
using vvll = vector<vll>;
using vvvll = vector<vvll>;
using vb = vector<bool>;
using vvb = vector<vb>;
using vvvb = vector<vvb>;
#define all(A) A.begin(),A.end()
#define rep(i, n) for (int i = 0; i < (int) (n); i++)
// template<class T>
// void chmax(T& p, T q) { if (p < q)p = q; };

#include<complex>
#define mod 1000000007
using ll = long long;
const int INF = 1000000000;
const ll LINF = 1001002003004005006ll;
int dx[] = { 1,0,-1,0 }, dy[] = { 0,1,0,-1 };
// ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
template<class T>bool chmax(T& a, const T& b) { if (a < b) { a = b; return true; }return false; }
template<class T>bool chmin(T& a, const T& b) { if (b < a) { a = b; return true; }return false; }

struct IOSetup {
    IOSetup() {
        cin.tie(0);
        ios::sync_with_stdio(0);
        cout << fixed << setprecision(12);
    }
} iosetup;

template<typename T>
ostream& operator<<(ostream& os, const vector<T>& v) {
    for (int i = 0; i < (int)v.size(); i++) os << v[i] << (i + 1 == (int)v.size() ? "" : " ");
    return os;
}
template<typename T>
istream& operator>>(istream& is, vector<T>& v) {
    for (T& x : v)is >> x;
    return is;
}

#line 1 "Geometry/template.cpp"
// Real
using Real = double;
const Real EPS = 1e-6;
const Real pi = acosl(-1);

// Point
using Point = complex<Real>;
istream& operator>>(istream& is, Point& p) {
    Real a, b;
    is >> a >> b;
    p = Point(a, b);
    return is;
}
ostream& operator<<(ostream& os, Point& p) {
    return os << fixed << setprecision(12) << p.real() << ' ' << p.imag();
}
inline bool eq(Real a, Real b) {
    return fabs(a - b) < EPS;
}
Point operator*(const Point& p, const Real& d) {
    return Point(real(p) * d, imag(p) * d);
}

// Line
struct Line {
    Point p1, p2;
    Line() = default;
    Line(Point p1, Point p2) :p1(p1), p2(p2) {}
    //Ax + By = C
    Line(Real A, Real B, Real C) {
        if (eq(A, 0))     p1 = Point(0, C / B), p2 = Point(1, C / B);
        else if (eq(B, 0))p1 = Point(C / A, 0), p2 = Point(C / A, 1);
        else            p1 = Point(0, C / B), p2 = Point(C / A, 0);
    }
};

// Segment
struct Segment :Line {
    Segment() = default;
    Segment(Point p1, Point p2) :Line(p1, p2) {}
};

Point rotate(Real theta, Point p) {
    return Point(cos(theta) * real(p) - sin(theta) * imag(p), sin(theta) * real(p) + cos(theta) * imag(p));
}
Real dot(Point a, Point b) {
    return real(a) * real(b) + imag(a) * imag(b);
}
#line 1 "Geometry/Cross.cpp"
// Cross
Real cross(Point a, Point b) {
    return real(a) * imag(b) - imag(a) * real(b);
}
#line 1 "Geometry/CounterClockWise.cpp"
// ccw (counter clockwise) (Requires: cross, dot)
//https://onlinejudge.u-aizu.ac.jp/courses/library/4/CGL/all/CGL_1_C
int ccw(Point a, Point b, Point c) {
    b -= a; c -= a;
    if (cross(b, c) > EPS)       return  1;//COUNTER CLOCKWISE
    else if (cross(b, c) < -EPS) return -1;//CLOCKWISE
    else if (dot(b, c) < 0)      return  2;//c--a--b ONLINE BACK
    else if (norm(b) < norm(c)) return -2;//a--b--c ONLINE FRONT
    else                     return  0;//a--c--b ON SEGMENT
}

Point projection(Segment l, Point p) {
    Real k = dot(l.p1 - l.p2, p - l.p1) / norm(l.p1 - l.p2);
    return l.p1 + (l.p1 - l.p2) * k;
}

bool intersect(Line l, Point p) {
    return abs(ccw(l.p1, l.p2, p)) != 1;
}
bool intersect(Segment s, Point p) {
    return ccw(s.p1, s.p2, p) == 0;
}
bool intersect(Segment s, Segment t) {
    return ccw(s.p1, s.p2, t.p1) * ccw(s.p1, s.p2, t.p2) <= 0 && ccw(t.p1, t.p2, s.p1) * ccw(t.p1, t.p2, s.p2) <= 0;
}
Real dis(Segment s, Point p) {
    Point r = projection(s, p);
    if (intersect(s, r)) return abs(r - p);
    return min(abs(s.p1 - p), abs(s.p2 - p));
}
Real dis(Segment a, Segment b) {
    if (intersect(a, b)) return 0;
    return min({ dis(a,b.p1),dis(a,b.p2),dis(b,a.p1),dis(b,a.p2) });
}

ll N,M,L;
void solve(ll N,ll M,ll L) {
    vector<Point> P(5*N);
    rep(i,N){
        double x,y,a,r;
        cin>>x>>y>>a>>r;
        rep(j,5)P[i*5+j]={x,y};
        Point Q={r,0};
        Q=rotate(a/180.0*pi+pi/2.0,Q);
        rep(j,5){
            P[i*5+j]+=Q;
            Q=rotate(pi/5.0*2.0,Q);
        }
    }
    vector<Segment> S(5*N);
    rep(i,N){
        rep(j,5){
            S[i*5+j]=Segment(P[i*5+j],P[i*5+(j+2)%5]);
        }
    }
    priority_queue<pair<double,ll>,vector<pair<double,ll>>,greater<pair<double,ll>>> Q;
    vector<double> D(5*N,1e18);
    rep(j,5){
        Q.push({0,M*5-5+j});
        D[M*5-5+j]=0.0;
    }
    vector<bool> seen(5*N,0);
    while(!Q.empty()){
        double d=Q.top().first;
        ll n=Q.top().second;
        Q.pop();
        if(seen[n])continue;
        seen[n]=1;
        rep(i,5*N){
            double nd=d+dis(S[i],S[n]);
            if(D[i]>nd+EPS){
                D[i]=nd;
                Q.push({D[i],i});
            }
        }
    }
    double an=1e18;
    rep(j,5)chmin(an,D[L*5-5+j]);
    cout<<fixed<<setprecision(15)<<an<<endl;

}

int main() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);

    
    while (cin >> N>>M>>L) {
        if (N == 0)return 0;
        solve(N,M,L);
    }

}
accuracy: 1
time_ms: 800
memory_kb: 3880
final_rank: 16

code_snippet: // (⁠◕⁠ᴗ⁠◕⁠✿⁠)

#pragma GCC target("avx2")
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
#define rep(i, n) for (ll i = 0; i < (n); i++)
#define srep(i, s, n) for (ll i = s; i < (n); i++)
#define len(x) ((int)(x).size())
#define all(x) (x).begin(), (x).end()
using namespace std;
template<typename T> using vc = vector<T>;
template<typename T> using vv = vc<vc<T>>;
template<typename T> using vvv = vv<vc<T>>;
using vi = vc<int>;using vvi = vv<int>; using vvvi = vv<vi>;
using ll = long long;using vl = vc<ll>;using vvl = vv<ll>; using vvvl = vv<vl>;
using ld = long double; using vld = vc<ld>; using vvld = vc<vld>; using vvvld = vc<vvld>;
using uint = unsigned int;
using ull = unsigned long long;
const ld pi = acos(-1.0);
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
// const ll mod = 1000000007;
const ll mod = 998244353;
inline bool inside(ll y, ll x, ll H, ll W) {return 0 <= (y) and (y) < (H) and 0 <= (x) and (x) < (W); }

#define debug(var)  do{std::cout << #var << " : \n";view(var);}while(0)
template<typename T> void view(T e){cout << e << endl;}
template<typename T> void view(const vc<T>& v){for(const auto& e : v){ cout << e << " "; } cout << endl;}
template<typename T> void view(const vv<T>& vv){ for(const auto& v : vv){ view(v); } }

using P = complex<ld>;
using L = pair<P, P>;
const ld eps = 1e-9;
ld dot(P a, P b){return (conj(a) * b).real();}
ld cross(P a, P b){return (conj(a) * b).imag();}

P proj(P a, L b){ //bに対するaの射影
    P p1 = b.second - b.first, p2 = a - b.first;
    return (dot(p1, p2) / (abs(p1) * abs(p1))) * p1 + b.first;
}
P refl(P a, L b){return a + P{2, 0} * (proj(a, b) - a);} //bに対するaの反射

int ccw(P a, P b, P c) { // 点の進行方向
  b -= a;  c -= a;
  if (cross(b,c) >  eps) return +1;  // counter clockwise
  if (cross(b,c) < -eps) return -1;  // clockwise
  if (dot(b,c)   < -eps) return +2;  // c--a--b on line
  if (abs(b) < abs(c)) return -2;    // a--b--c on line or a==b
  return 0;                          // a--c--b on line or a==c or b==c
}

// 直線と点
bool isecLP(P a1, P a2, P b) {
  return abs(ccw(a1, a2, b)) != 1;  // return EQ(cross(a2-a1, b-a1), 0); と等価
}

// 直線と直線
bool isecLL(P a1, P a2, P b1, P b2) {
  return !isecLP(a2-a1, b2-b1, 0) || isecLP(a1, b1, b2);
}

// 直線と線分
bool isecLS(P a1, P a2, P b1, P b2) {
  return cross(a2-a1, b1-a1) * cross(a2-a1, b2-a1) < eps;
}

// 線分と線分
bool isecSS(P a1, P a2, P b1, P b2) {
  return ccw(a1, a2, b1)*ccw(a1, a2, b2) <= 0 &&
         ccw(b1, b2, a1)*ccw(b1, b2, a2) <= 0;
}

// 線分と点
bool isecSP(P a1, P a2, P b) {
  return !ccw(a1, a2, b);
}

ld distLP(P a1, P a2, P p) {
  return abs(proj(p, {a1, a2}) - p);
}

ld distLL(P a1, P a2, P b1, P b2) {
  return isecLL(a1, a2, b1, b2) ? 0 : distLP(a1, a2, b1);
}

ld distLS(P a1, P a2, P b1, P b2) {
  return isecLS(a1, a2, b1, b2) ? 0 : min(distLP(a1, a2, b1), distLP(a1, a2, b2));
}

ld distSP(P a1, P a2, P p) {
  P r = proj(p, {a1, a2});
  if (isecSP(a1, a2, r)) return abs(r-p);
  return min(abs(a1-p), abs(a2-p));
}

ld distSS(P a1, P a2, P b1, P b2) {
  if (isecSS(a1, a2, b1, b2)) return 0;
  return min(min(distSP(a1, a2, b1), distSP(a1, a2, b2)),
             min(distSP(b1, b2, a1), distSP(b1, b2, a2)));
}

tuple<vld, vi> dijkstra(int s, int N, vv<pair<int, ld>> &g){
    using P = pair<ld, int>;
    vld dist(N, INF);
    vi bef(N);
    priority_queue<P, vc<P>, greater<P>> q;
    dist[s] = 0;
    q.push({0, s});
    while (!q.empty()){
        auto [c, now] = q.top();q.pop();
        if (dist[now] < c) continue;
        for (auto&& [nxt, nc]: g[now]) if (dist[nxt] > c + nc){
            dist[nxt] = c + nc;
            bef[nxt] = now;
            q.push({dist[nxt], nxt}); 
        }
    }
    return {dist, bef};
}


ld solve(int N, int s, int t){
    vc<tuple<P, ld, ld>> point;
    rep(i, N){
        ld x, y, a, r; cin >> x >> y >> a >> r;
        point.push_back({{x, y}, a, r});
    }
    vv<pair<int, ld>> g(N);
    vv<P> ele;
    for (auto [o, a, r] : point){
        ele.push_back({});
        rep(i, 5){
            ele.back().push_back(o + P{r, 0} * P{cos((90 + a + 144 * i) * pi / (ld)180), sin((90 + a + 144 * i) * pi / (ld)180)});
        }
    }
    rep(j, N) rep(i, j){
        ld mini = inf;
        rep(a, 5) rep(b, 5){
            mini = min(mini, abs(ele[i][a] - ele[j][b]));
            mini = min(mini, distSP(ele[i][a], ele[i][(a + 1) % 5], ele[j][b]));
            mini = min(mini, distSP(ele[j][b], ele[j][(b + 1) % 5], ele[i][a]));
            if (isecSS(ele[i][a], ele[i][(a + 1) % 5], ele[j][b], ele[j][(b + 1) % 5])) mini = 0;
        }
        g[i].push_back({j, mini});
        g[j].push_back({i, mini});
    }
    auto [dist, bef] = dijkstra(s, N, g);
    return dist[t];
}

int main(){
    vc<ld> ans;
    while (true){
        int N, s, t; cin >> N >> s >> t; s--; t--;
        if (N == 0) break;
        ans.push_back(solve(N, s, t));
    }
    for (auto x : ans) cout << fixed << setprecision(16) << x << endl;
}
accuracy: 1
time_ms: 700
memory_kb: 4320
final_rank: 14

code_snippet: #include <bits/stdc++.h>





namespace zawa {

using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;
using i128 = __int128_t;

using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

using usize = std::size_t;

} // namespace zawa


namespace zawa {

namespace geometryR2 {

using Real = long double;
constexpr Real EPS{1e-12};

namespace internal {

constexpr i32 negative{-1};
constexpr i32 zero{};
constexpr i32 positive{1};

} // namespace internal

constexpr i32 Sign(Real value) {
    if (value < -EPS) return internal::negative;
    if (value > EPS) return internal::positive;
    return internal::zero;
}

constexpr bool Zero(Real value) {
    return Sign(value) == internal::zero;
}

constexpr bool Positive(Real value) {
    return Sign(value) == internal::positive;
}

constexpr bool Negative(Real value) {
    return Sign(value) == internal::negative;
}

constexpr bool Equal(Real a, Real b) {
    return Zero(a - b);
}

constexpr bool Smaller(Real a, Real b) {
    return Negative(a - b);
}

constexpr bool Bigger(Real a, Real b) {
    return Positive(a - b);
}

constexpr Real Square(Real value) {
    return (Zero(value) ? value : value * value);
}

constexpr Real Sqrt(Real value) {
    assert(!Negative(value));
    return (Zero(value) ? value : sqrtl(value));
}

constexpr Real Abs(Real value) {
    return (Negative(value) ? -value : value);
}

} // namespace geometryR2
 
} // namespace zawa



namespace zawa {

namespace geometryR2 {

constexpr Real PI{acosl(-1)};
constexpr Real TAU{static_cast<Real>(2) * PI};

constexpr Real ArcToRadian(Real arc) {
    return (arc * PI) / static_cast<Real>(180);
}

constexpr Real RadianToArc(Real radian) {
    return (radian * static_cast<Real>(180)) / PI;
}

} // namespace geometryR2

} // namespace zawa


namespace zawa {

namespace geometryR2 {

class Point {
private:
    Real x_{}, y_{};
public:
    /* constructor */
    Point() = default;
    Point(Real x, Real y) : x_{x}, y_{y} {}

    /* getter, setter */
    Real x() const {
        return x_;
    }
    Real& x() {
        return x_;
    }
    Real y() const {
        return y_;
    }
    Real& y() {
        return y_;
    }

    /* operator */
    Point& operator+=(const Point& rhs) {
        x_ += rhs.x();
        y_ += rhs.y();
        return *this;
    }
    friend Point operator+(const Point& lhs, const Point& rhs) {
        return Point{lhs} += rhs;
    }
    Point operator+() const {
        return *this;
    }
    Point& operator-=(const Point& rhs) {
        x_ -= rhs.x();
        y_ -= rhs.y();
        return *this;
    }
    friend Point operator-(const Point& lhs, const Point& rhs) {
        return Point{lhs} -= rhs;
    }
    Point operator-() const {
        return Point{} - *this;
    }
    Point& operator*=(Real k) {
        x_ *= k;
        y_ *= k;
        return *this;
    }
    friend Point operator*(Real k, const Point& p) {
        return Point{p} *= k;
    }
    friend Point operator*(const Point& p, Real k) {
        return Point{p} *= k;
    }
    Point& operator/=(Real k) {
        assert(!Zero(k));
        x_ /= k;
        y_ /= k;
        return *this;
    }
    friend Point operator/(Real k, const Point& p) {
        return Point{p} /= k;
    }
    friend Point operator/(const Point& p, Real k) {
        return Point{p} /= k;
    }
    friend bool operator==(const Point& lhs, const Point& rhs) {
        return Equal(lhs.x(), rhs.x()) and Equal(lhs.y(), rhs.y());
    }
    friend bool operator!=(const Point& lhs, const Point& rhs) {
        return !Equal(lhs.x(), rhs.x()) or !Equal(lhs.y(), rhs.y());
    }
    friend bool operator<(const Point& lhs, const Point& rhs) {
        return Smaller(lhs.x(), rhs.x()) or 
            (Equal(lhs.x(), rhs.x()) and Smaller(lhs.y(), rhs.y()));
    }
    friend bool operator<=(const Point& lhs, const Point& rhs) {
        return Smaller(lhs.x(), rhs.x()) or 
            (Equal(lhs.x(), rhs.x()) and (Smaller(lhs.y(), rhs.y()) or Equal(lhs.y(), rhs.y())));
    }
    friend bool operator>(const Point& lhs, const Point& rhs) {
        return Bigger(lhs.x(), rhs.x()) or
            (Equal(lhs.x(), rhs.x()) and Bigger(lhs.y(), rhs.y()));
    }
    friend bool operator>=(const Point& lhs, const Point& rhs) {
        return Bigger(lhs.x(), rhs.x()) or
            (Equal(lhs.x(), rhs.x()) and (Bigger(lhs.y(), rhs.y()) or Equal(lhs.y(), rhs.y())));
    }
    friend std::istream& operator>>(std::istream& is, Point& p) {
        is >> p.x_ >> p.y_;
        return is;
    }
    friend std::ostream& operator<<(std::ostream& os, const Point& p) {
        os << '(' << p.x_ << ',' << p.y_ << ')';
        return os;
    }
    
    /* member function */
    Real normSquare() const {
        return Square(x_) + Square(y_);
    }
    Real norm() const {
        return Sqrt(normSquare());
    }
    void normalize() {
        assert((*this) != Point{});
        (*this) /= norm(); 
    }
    Point normalized() const {
        Point res{*this};
        res.normalize();
        return res;
    }
    Point rotated(Real radian) const {
        return Point{
            x_ * cosl(radian) - y_ * sinl(radian),
            x_ * sinl(radian) + y_ * cosl(radian)
        };
    }
    void rotate(Real radian) {
        *this = rotated(radian); 
    }
    Point rotatedByArc(Real arc) const {
        return rotated(ArcToRadian(arc));
    }
    void rotateByArc(Real arc) {
        *this = rotatedByArc(arc);
    }
    Real argument() const {
        return (Negative(y_) ? TAU : static_cast<Real>(0)) + atan2l(y_, x_);
    }
    Real argumentByArc() const {
        return RadianToArc(argument());
    }

    /* friend function */
    friend Real Dot(const Point& lhs, const Point& rhs) {
        return lhs.x() * rhs.x() + lhs.y() * rhs.y();
    }
    friend Real Cross(const Point& lhs, const Point& rhs) {
        return lhs.x() * rhs.y() - lhs.y() * rhs.x();
    }
    friend Real Argument(const Point& lhs, const Point& rhs) {
        return rhs.argument() - lhs.argument();
    }
    friend bool ArgComp(const Point& lhs, const Point& rhs) {
        return Smaller(lhs.argument(), rhs.argument());
    }
};

using Vector = Point;

} // namespace geometryR2

} // namespace zawa



namespace zawa {

namespace geometryR2 {

enum RELATION {
    // p0 -> p1 -> p2の順で直線上に並んでいる
    ONLINE_FRONT = -2,
    // (p1 - p0) -> (p2 - p0)が時計回りになっている
    CLOCKWISE,
    // p0 -> p2 -> p1の順で直線上に並んでいる
    ON_SEGMENT,
    // (p1 - p0) -> (p2 - p0)が反時計回りになっている
    COUNTER_CLOCKWISE,
    // p2 -> p0 -> p1、またはp1 -> p0 -> p2の順で直線上に並んでいる
    ONLINE_BACK
};

RELATION Relation(const Point& p0, const Point& p1, const Point& p2) {
    Point a{p1 - p0}, b{p2 - p0};
    if (Positive(Cross(a, b))) return COUNTER_CLOCKWISE;
    if (Negative(Cross(a, b))) return CLOCKWISE;
    if (Negative(Dot(a, b))) return ONLINE_BACK;
    if (Smaller(a.normSquare(), b.normSquare())) return ONLINE_FRONT;
    return ON_SEGMENT;
};

} // namespace geometryR2

} // namespace zawa


namespace zawa {

namespace geometryR2 {

Real Distance(const Point& p0, const Point& p1) {
    return Point{p1 - p0}.norm();
}

Real DistanceSquare(const Point& p0, const Point& p1) {
    return Point{p1 - p0}.normSquare();
}

} // namespace geometryR2

} // namespace zawa


namespace zawa {

namespace geometryR2 {

class Segment {
private:
    Point p0_{}, p1_{};
public:
    /* constructor */
    Segment() = default;
    Segment(const Point& p0, const Point& p1) : p0_{p0}, p1_{p1} {}
    Segment(Real x0, Real y0, Real x1, Real y1) : p0_{x0, y0}, p1_{x1, y1} {}

    /* getter setter */
    const Point& p0() const {
        return p0_;
    }
    Point& p0() {
        return p0_;
    }
    const Point& p1() const {
        return p1_;
    }
    Point& p1() {
        return p1_;
    }

    /* member function */
    bool valid() const {
        return p0_ != p1_;
    }
    bool straddle(const Segment& s) const {
        return Relation(p0_, p1_, s.p0()) * Relation(p0_, p1_, s.p1()) <= 0;
    }
    Real length() const {
        assert(valid());
        return Distance(p0_, p1_);
    }
};

} // namespace geometryR2

} // namespace zawa




namespace zawa {

namespace geometryR2 {

bool Intersect(const Segment& s0, const Segment& s1) {
    assert(s0.valid());
    assert(s1.valid());
    return s0.straddle(s1) and s1.straddle(s0);
}

} // namespace geometryR2

} // namespace zawa



namespace zawa {

namespace geometryR2 {

Real Distance(const Point& p, const Segment& s) {
    assert(s.valid());
    if (Negative(Dot(s.p1() - s.p0(), p - s.p0()))) {
        return Distance(p, s.p0());
    }
    if (Negative(Dot(s.p0() - s.p1(), p - s.p1()))) {
        return Distance(p, s.p1());
    }
    return Abs(Cross(s.p1() - s.p0(), p - s.p0())) / s.length();
}

bool PointOnSegment(const Point& p, const Segment& s) {
    assert(s.valid());
    return Zero(Distance(p, s));
}

} // namespace geometryR2

} // namespace zawa


namespace zawa {

namespace geometryR2 {

Real Distance(const Segment& s0, const Segment& s1) {
    assert(s0.valid());
    assert(s1.valid());
    if (Intersect(s0, s1)) {
        return static_cast<Real>(0);
    }
    else {
        return std::min({ 
                Distance(s1.p0(), s0), 
                Distance(s1.p1(), s0),
                Distance(s0.p0(), s1),
                Distance(s0.p1(), s1) });
    }
}

} // namespace geometryR2

} // namespace zawa

using namespace zawa::geometryR2;

struct Star {
    Point vs[5];
    Star() = default;
    Star(const Point& center, Real arg, Real r) {
        Point tmp[5];
        Point dir{0, r};
        for (int i{} ; i < 5 ; i++) {
            tmp[i] = dir;
            dir.rotateByArc(-(360 / 5));
        }
        for (auto& t : tmp) t.rotateByArc(arg);
        for (auto& t : tmp) t += center;
        vs[0] = tmp[0];
        vs[1] = tmp[2];
        vs[2] = tmp[4];
        vs[3] = tmp[1];
        vs[4] = tmp[3];
    }

    Segment operator[](int i) const {
        return Segment{vs[i], vs[i==4?0:i+1]};
    }
};

Real distance(const Star& s0, const Star& s1) {
    Real res{1e18};
    for (int i{} ; i < 5 ; i++) {
        for (int j{} ; j < 5 ; j++) {
            res = std::min(res, Distance(s0[i], s1[j]));
        }
    }
    return res;
}

bool solve() {
    int n, m, l; std::cin >> n >> m >> l;
    if (n == 0) return false;
    m--; l--;
    std::vector<Star> stars(n);
    Star(Point{0, 0}, 0, 1);
    for (int i{} ; i < n ; i++) {
        Point p; std::cin >> p;
        Real a, r; std::cin >> a >> r;
        stars[i] = Star{p, a, r};
    }
    std::vector g(n, std::vector<Real>(n, 1e18));
    for (int i{} ; i < n ; i++) for (int j{} ; j < n ; j++) {
        if (i == j) g[i][j] = 0;
        else g[i][j] = distance(stars[i], stars[j]);
    }
    // for (int i{} ; i < n ; i++) {
    //     for (int j{} ; j < n ; j++) {
    //         std::cerr << g[i][j] << (j + 1 == n ? '\n' : ' ');
    //     }
    // }
    for (int k{} ; k < n ; k++) for (int i{} ; i < n ; i++) for (int j{} ; j < n ; j++) {
        g[i][j] = std::min(g[i][j], g[i][k] + g[k][j]);
    }
    Real ans{g[m][l]};
    std::cout << ans << '\n';
    return true;
}

int main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    std::cout << std::fixed << std::setprecision(10);

    for (int i{} ; solve() ; i++) {
        // std::cerr << "case " << i + 1 << " end-----------\n"; 
    }
}
accuracy: 1
time_ms: 420
memory_kb: 4172
final_rank: 12

code_snippet: #include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < n; ++i)
typedef long long ll;
using namespace std;

using Real = double;
using Point = complex<Real>;
const Real EPS = 1e-8, PI = acos(-1.0);

Point operator*(const Point &p, const Real &d) { return Point(real(p) * d, imag(p) * d); }
Point operator/(const Point &p, const Real &d) { return Point(real(p) / d, imag(p) / d); }
int sign(const Real &r) {
    if (r <= -EPS) return -1;
    if (r >= EPS) return 1;
    return 0;
}
bool equals(const Real &a, const Real &b) { return sign(a - b) == 0; }
bool operator<(const Point &a, const Point &b) { return real(a) != real(b) ? real(a) < real(b) : imag(a) < imag(b); }
Real dot(const Point &a, const Point &b) { return real(conj(a) * b); }
Real cross(const Point &a, const Point &b) { return imag(conj(a) * b); }
struct Line {
    Point a, b;
    Line() = default;
    Line(Point a, Point b) : a(a), b(b) {}
};
Point projection(const Line &l, const Point &p) { return l.a + (l.a - l.b) * dot(p - l.a, l.a - l.b) / norm(l.a - l.b); }
Point reflection(const Line &l, const Point &p) { return p * (projection(l, p) - p) * 2.0; }
int ccw(const Point &a, Point b, Point c) {
    b -= a, c -= a;
    if (sign(cross(b, c)) == 1) return 1;
    if (sign(cross(b, c)) == -1) return -1;
    if (sign(dot(b, c)) == -1) return 2;
    if (norm(b) < norm(c)) return -2;
    return 0;
}
bool is_intersect_ss(const Line &s, const Line &t) {
    return ccw(s.a, s.b, t.a) * ccw(s.a, s.b, t.b) <= 0 &&
           ccw(t.a, t.b, s.a) * ccw(t.a, t.b, s.b) <= 0;
}
Real distance_sp(const Line &s, const Point &p) {
    Point r = projection(s, p);
    if (ccw(s.a, s.b, r) == 0) return abs(r - p);
    return min(abs(s.a - p), abs(s.b - p));
}
Real distance_ss(const Line &a, const Line &b) {
    if (is_intersect_ss(a, b)) return 0;
    return min({distance_sp(a, b.a), distance_sp(a, b.b), distance_sp(b, a.a), distance_sp(b, a.b)});
}

template <class T>
struct edge {
    int from, to;
    T cost;
    edge(int to, T cost = {}) : from(-1), to(to), cost(cost) {}
};

void solve(int N, int M, int L) {
    M--, L--;
    vector<vector<Line>> lines(N);
    rep(i, N) {
        int X, Y, A, R;
        cin >> X >> Y >> A >> R;

        vector<Point> ps(5);
        rep(j, 5) {
            Real rad = ((A + 72 * j + 90) % 360) * PI / 180;
            ps[j] = Point(X + R * cos(rad), Y + R * sin(rad));
        }
        rep(j, 5) lines[i].emplace_back(ps[j], ps[(j + 2) % 5]);
    }

    vector<vector<edge<Real>>> G(N);
    rep(i, N) rep(j, i) {
        Real d = 1e18;
        rep(k, 5) rep(l, 5) d = min(d, distance_ss(lines[i][k], lines[j][l]));
        G[i].emplace_back(j, d);
        G[j].emplace_back(i, d);
    }

    using T = Real;
    auto dijkstra = [](auto &G, int s, T mx) {
        using P = pair<T, int>;

        int N = G.size();
        vector<T> d(N, mx);
        d[s] = 0;

        priority_queue<P, vector<P>, greater<P>> pq;
        pq.push({0, s});
        while (!pq.empty()) {
            auto [dist, u] = pq.top();
            pq.pop();

            if (d[u] < dist) continue;
            for (auto &e : G[u]) {
                int v = e.to;
                T cost = e.cost;
                if (d[v] > d[u] + cost) {
                    d[v] = d[u] + cost;
                    pq.push({d[v], v});
                }
            }
        }
        return d;
    };
    auto dist = dijkstra(G, M, 1e18);
    printf("%.10f\n", dist[L]);
}

int main() {
    cin.tie(0)->sync_with_stdio(0);

    int N, M, L;
    while (cin >> N >> M >> L) {
        if (N == 0 && M == 0 && L == 0) continue;
        solve(N, M, L);
    }

    return 0;
}
accuracy: 1
time_ms: 390
memory_kb: 4080
final_rank: 10

code_snippet: #include<bits/stdc++.h>
using namespace std;
const double EPS = 1e-10;
double dist(double x, double y){
    return sqrt(x*x+y*y);
}
double d(double lx, double ly, double rx, double ry, double x, double y){
    if(lx==rx){
        swap(lx, ly);
        swap(rx, ry);
        swap(x, y);
    }
    lx -= EPS;
    rx += EPS;
    double a = (ly-ry)/(lx-rx);
    double b = ly-a*lx;
    if(lx > rx) swap(lx, rx);
    while(fabs(lx-rx)>EPS){
        double d1 = dist(lx-x, a*lx+b-y);
        double d2 = dist(rx-x, a*rx+b-y);
        if(d1 > d2) lx = (lx+rx)*0.5;
        else rx = (lx+rx)*0.5;
    }
    return dist(lx-x, a*lx+b-y);
}
bool crs(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3){
    double c0 = (x2-x3)*(y0-y2)+(x2-x0)*(y2-y3);
    double c1 = (x2-x3)*(y1-y2)+(x2-x1)*(y2-y3);
    double c2 = (x0-x1)*(y2-y0)+(x0-x2)*(y0-y1);
    double c3 = (x0-x1)*(y3-y0)+(x0-x3)*(y0-y1);
    return c0*c1<0 && c2*c3<0;
}
int main(){
    int n, s, t;
    double x[102], y[102], a[102], r[102];
    double edge[102][102];
    while(cin >> n >> s >> t, n){
        for(int i=1;i<=n;i++){
            cin >> x[i] >> y[i] >> a[i] >> r[i];
            edge[i][i] = 0;
        }
        for(int i=1;i<=n-1;i++){
            for(int j=i+1;j<=n;j++){
                edge[i][j] = 1000000;
                for(int k=0;k<5;k++){
                    for(int l=0;l<5;l++){
                        double p1 = a[i]*M_PI/180 + M_PI*0.5 + M_PI*k*0.4;
                        double p2 = a[i]*M_PI/180 + M_PI*0.5 + M_PI*((k+2)%5)*0.4;
                        double q1 = a[j]*M_PI/180 + M_PI*0.5 + M_PI*l*0.4;
                        double q2 = a[j]*M_PI/180 + M_PI*0.5 + M_PI*((l+2)%5)*0.4;
                        double px1 = x[i] + r[i]*cos(p1);
                        double py1 = y[i] + r[i]*sin(p1);
                        double px2 = x[i] + r[i]*cos(p2);
                        double py2 = y[i] + r[i]*sin(p2);
                        double qx1 = x[j] + r[j]*cos(q1);
                        double qy1 = y[j] + r[j]*sin(q1);
                        double qx2 = x[j] + r[j]*cos(q2);
                        double qy2 = y[j] + r[j]*sin(q2);
                        if(crs(px1, py1, px2, py2, qx1, qy1, qx2, qy2)) edge[i][j] = 0;
                        else{
                            edge[i][j] = min(edge[i][j], d(px1, py1, px2, py2, qx1, qy1));
                            edge[i][j] = min(edge[i][j], d(px1, py1, px2, py2, qx2, qy2));
                            edge[i][j] = min(edge[i][j], d(qx1, qy1, qx2, qy2, px1, py1));
                            edge[i][j] = min(edge[i][j], d(qx1, qy1, qx2, qy2, px2, py2));
                        }
                        edge[j][i] = edge[i][j];
                    }
                }
            }
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=n;k++){
                    edge[j][k] = min(edge[j][k], edge[j][i]+edge[i][k]);
                }
            }
        }
        printf("%.10lf\n", edge[s][t]);
    }
    return(0);
}
accuracy: 1
time_ms: 2480
memory_kb: 3676
final_rank: 19

code_snippet: #include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

class point2d {
public:
	double x, y;
	point2d() : x(0.0), y(0.0) {}
	point2d(double x_, double y_) : x(x_), y(y_) {}
	point2d& operator+=(const point2d& p) { x += p.x; y += p.y; return *this; }
	point2d& operator-=(const point2d& p) { x -= p.x; y -= p.y; return *this; }
	point2d& operator*=(double v) { x *= v; y *= v; return *this; }
	point2d& operator/=(double v) { x /= v; y /= v; return *this; }
	point2d operator+(const point2d& p) const { return point2d(*this) += p; }
	point2d operator-(const point2d& p) const { return point2d(*this) -= p; }
	point2d operator*(double v) { return point2d(*this) *= v; }
	point2d operator/(double v) { return point2d(*this) /= v; }
	double abs() const {
		return sqrt(x * x + y * y);
	}
	double dot(const point2d& p) const {
		return x * p.x + y * p.y;
	}
	double cross(const point2d& p) const {
		return x * p.y - y * p.x;
	}
};

const double pi = acos(-1.0);

double segment_point(const point2d& p1, const point2d& p2, const point2d& p3) {
	if ((p2 - p1).dot(p3 - p1) < 0.0) {
		return (p3 - p1).abs();
	}
	if ((p1 - p2).dot(p3 - p2) < 0.0) {
		return (p3 - p2).abs();
	}
	return abs((p2 - p1).cross(p3 - p1)) / (p2 - p1).abs();
}

bool check_cross(const point2d& p1, const point2d& p2, const point2d& p3, const point2d& p4) {
	auto ccw = [](const point2d& v1, const point2d& v2) -> int {
		double cross = v1.cross(v2);
		if (cross < 0.0) return +1;
		if (cross > 0.0) return -1;
		if (v1.dot(v2) < 0.0) return +2;
		if (v1.abs() < v2.abs()) return -2;
		return 0;
	};
	return ccw(p2 - p1, p3 - p1) * ccw(p2 - p1, p4 - p1) <= 0 && ccw(p4 - p3, p1 - p3) * ccw(p4 - p3, p2 - p3) <= 0;
}

double segment_segment(const point2d& p1, const point2d& p2, const point2d& p3, const point2d& p4) {
	if (check_cross(p1, p2, p3, p4)) {
		return 0.0;
	}
	double d1 = segment_point(p1, p2, p3);
	double d2 = segment_point(p1, p2, p4);
	double d3 = segment_point(p3, p4, p1);
	double d4 = segment_point(p3, p4, p2);
	return min({ d1, d2, d3, d4 });
}

int main() {
	cout.precision(15);
	int N, S, T;
	while (true) {
		cin >> N >> S >> T;
		if (N == 0 && S == 0 && T == 0) {
			break;
		}
		S -= 1;
		T -= 1;
		vector<int> X(N), Y(N), A(N), R(N);
		for (int i = 0; i < N; i++) {
			cin >> X[i] >> Y[i] >> A[i] >> R[i];
		}
		vector<point2d> P(5 * N);
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < 5; j++) {
				double arg = (A[i] + 144 * j + 90) * (pi / 180.0);
				P[5 * i + j] = point2d(X[i], Y[i]) + point2d(cos(arg), sin(arg)) * R[i];
			}
		}
		auto next = [&](int id) {
			return (id % 5 != 4 ? id + 1 : id - 4);
		};
		vector<vector<double> > d(N, vector<double>(N));
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if (i == j) {
					d[i][j] = 0.0;
				}
				if (i > j) {
					d[i][j] = d[j][i];
				}
				if (i < j) {
					d[i][j] = 1.0e+99;
					for (int k = 0; k < 5; k++) {
						for (int l = 0; l < 5; l++) {
							d[i][j] = min(d[i][j], segment_segment(P[5 * i + k], P[next(5 * i + k)], P[5 * j + l], P[next(5 * j + l)]));
						}
					}
				}
			}
		}
		vector<double> dist(N, 1.0e+99);
		dist[S] = 0.0;
		vector<bool> vis(N, false);
		for (int i = 0; i < N; i++) {
			int id = -1;
			for (int j = 0; j < N; j++) {
				if (!vis[j] && (id == -1 || dist[id] > dist[j])) {
					id = j;
				}
			}
			vis[id] = true;
			for (int j = 0; j < N; j++) {
				dist[j] = min(dist[j], dist[id] + d[id][j]);
			}
		}
		cout << fixed << dist[T] << endl;
	}
	return 0;
}
accuracy: 1
time_ms: 50
memory_kb: 3732
final_rank: 1

code_snippet: /*

Thanks for ei1333

*/

#include <iostream>
#include <vector>
#include <algorithm>
#include <deque>
#include <queue>
#include <string>
#include <iomanip>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <utility>
#include <stack>
#include <random>
#include <complex>
#include <functional>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#include <assert.h>
// #if __has_include(<atcoder/all>)
// #include <atcoder/all>
// using namespace atcoder;
// #endif
using namespace std;
using ll=long long;
#define read(x) cin>>(x);
#define readll(x) ll (x);cin>>(x);
#define readS(x) string (x);cin>>(x);
#define readvll(x,N) vector<ll> (x)((N));for(int i=0;i<(N);i++){cin>>(x)[i];}
#define rep(i,N) for(ll (i)=0;(i)<(N);(i)++)
#define rep2d(i,j,H,W) for(ll (i)=0;(i)<(H);(i)++)for(ll (j)=0;(j)<(W);j++)
#define is_in(x,y) (0<=(x) && (x)<H && 0<=(y) && (y)<W)
#define yn {cout<<"Yes"<<endl;}else{cout<<"No"<<endl;}
#define double_out(x) fixed << setprecision(x)
inline ll powll(ll x,ll n){ll r=1;while(n>0){if(n&1){r*=x;};x*=x;n>>=1;};return r;}

using Real = double;
using Point = complex< Real >;
const Real EPS = 1e-8, PI = acos(-1);

inline bool eq(Real a, Real b) { return fabs(b - a) < EPS; }

Point operator*(const Point &p, const Real &d) {
  return Point(real(p) * d, imag(p) * d);
}

istream &operator>>(istream &is, Point &p) {
  Real a, b;
  is >> a >> b;
  p = Point(a, b);
  return is;
}

ostream &operator<<(ostream &os, Point &p) {
  return os << fixed << setprecision(10) << p.real() << " " << p.imag();
}

// 点 p を反時計回りに theta 回転
Point rotate(Real theta, const Point &p) {
  return Point(cos(theta) * p.real() - sin(theta) * p.imag(), sin(theta) * p.real() + cos(theta) * p.imag());
}

Real radian_to_degree(Real r) {
  return (r * 180.0 / PI);
}

Real degree_to_radian(Real d) {
  return (d * PI / 180.0);
}

// a-b-c の角度のうち小さい方を返す
Real get_angle(const Point &a, const Point &b, const Point &c) {
  const Point v(b - a), w(c - b);
  Real alpha = atan2(v.imag(), v.real()), beta = atan2(w.imag(), w.real());
  if(alpha > beta) swap(alpha, beta);
  Real theta = (beta - alpha);
  return min(theta, 2 * acos(-1) - theta);
}

namespace std {
  bool operator<(const Point &a, const Point &b) {
    return a.real() != b.real() ? a.real() < b.real() : a.imag() < b.imag();
  }
}


struct Line {
  Point a, b;

  Line() = default;

  Line(Point a, Point b) : a(a), b(b) {}

  Line(Real A, Real B, Real C) // Ax + By = C
  {
    if(eq(A, 0)) a = Point(0, C / B), b = Point(1, C / B);
    else if(eq(B, 0)) b = Point(C / A, 0), b = Point(C / A, 1);
    else a = Point(0, C / B), b = Point(C / A, 0);
  }

  friend ostream &operator<<(ostream &os, Line &p) {
    return os << p.a << " to " << p.b;
  }

  friend istream &operator>>(istream &is, Line &a) {
    return is >> a.a >> a.b;
  }
};

// 線分
struct Segment : Line {
  Segment() = default;

  Segment(Point a, Point b) : Line(a, b) {}
};

struct Circle {
  Point p;
  Real r;

  Circle() = default;

  Circle(Point p, Real r) : p(p), r(r) {}

  friend ostream &operator<<(ostream &os,Circle &a){
    return os << "( " << a.p.real() << " , " << a.p.imag() << " ) r: "<< a.r ;
  }

  friend istream &operator>>(istream &is,Circle &a){
    return is >> a.p >> a.r;
  }
};

using Points = vector< Point >;
using Polygon = vector< Point >;
using Segments = vector< Segment >;
using Lines = vector< Line >;
using Circles = vector< Circle >;

// 外積
Real cross(const Point &a, const Point &b) {
  return real(a) * imag(b) - imag(a) * real(b);
}

// 内積
Real dot(const Point &a, const Point &b) {
  return real(a) * real(b) + imag(a) * imag(b);
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_1_C
// 点の回転方向
int ccw(const Point &a, Point b, Point c) {
  b = b - a, c = c - a;
  if(cross(b, c) > EPS) return +1;  // "COUNTER_CLOCKWISE" // p0,p1,p2が反時計回りになる場合
  if(cross(b, c) < -EPS) return -1; // "CLOCKWISE"         // p0,p1,p2が時計回りになる場合
  if(dot(b, c) < 0) return +2;      // "ONLINE_BACK"       // p2,p0,p1がこの順で同一直線上にある場合
  if(norm(b) < norm(c)) return -2;  // "ONLINE_FRONT"      // p0,p1,p2がこの順で同一直線上にある場合
  return 0;                         // "ON_SEGMENT"        // p2が線分p0,p1上にある場合
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_2_A
// 平行判定
bool parallel(const Line &a, const Line &b) {
  return eq(cross(a.b - a.a, b.b - b.a), 0.0);
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_2_A
// 垂直判定
bool orthogonal(const Line &a, const Line &b) {
  return eq(dot(a.a - a.b, b.a - b.b), 0.0);
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_1_A
// 射影
// 直線 l に p から垂線を引いた交点を求める
Point projection(const Line &l, const Point &p) {
  double t = dot(p - l.a, l.a - l.b) / norm(l.a - l.b);
  return l.a + (l.a - l.b) * t;
}

Point projection(const Segment &l, const Point &p) {
  double t = dot(p - l.a, l.a - l.b) / norm(l.a - l.b);
  return l.a + (l.a - l.b) * t;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_1_B
// 反射
// 直線 l を対称軸として点 p  と線対称にある点を求める
Point reflection(const Line &l, const Point &p) {
  return p + (projection(l, p) - p) * 2.0;
}

// 交差
bool intersect(const Line &l, const Point &p) {
  return abs(ccw(l.a, l.b, p)) != 1;
}

bool intersect(const Line &l, const Line &m) {
  return abs(cross(l.b - l.a, m.b - m.a)) > EPS || abs(cross(l.b - l.a, m.b - l.a)) < EPS;
}

bool intersect(const Segment &s, const Point &p) {
  return ccw(s.a, s.b, p) == 0;
}

bool intersect(const Line &l, const Segment &s) {
  return cross(l.b - l.a, s.a - l.a) * cross(l.b - l.a, s.b - l.a) < EPS;
}

Real distance(const Line &l, const Point &p);

bool intersect(const Circle &c, const Line &l) {
  return distance(l, c.p) <= c.r + EPS;
}

bool intersect(const Circle &c, const Point &p) {
  return abs(abs(p - c.p) - c.r) < EPS;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_2_B
bool intersect(const Segment &s, const Segment &t) {
  return ccw(s.a, s.b, t.a) * ccw(s.a, s.b, t.b) <= 0 && ccw(t.a, t.b, s.a) * ccw(t.a, t.b, s.b) <= 0;
}

int intersect(const Circle &c, const Segment &l) {
  if(norm(projection(l, c.p) - c.p) - c.r * c.r > EPS) return 0;
  auto d1 = abs(c.p - l.a), d2 = abs(c.p - l.b);
  if(d1 < c.r + EPS && d2 < c.r + EPS) return 0;
  if(d1 < c.r - EPS && d2 > c.r + EPS || d1 > c.r + EPS && d2 < c.r - EPS) return 1;
  const Point h = projection(l, c.p);
  if(dot(l.a - h, l.b - h) < 0) return 2;
  return 0;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_7_A&lang=jp
int intersect(Circle c1, Circle c2) {
  if(c1.r < c2.r) swap(c1, c2);
  Real d = abs(c1.p - c2.p);
  if(c1.r + c2.r < d) return 4;// 離れている (共通接線 4)
  if(eq(c1.r + c2.r, d)) return 3;// 外接する (共通接線 3)
  if(c1.r - c2.r < d) return 2;// 交わる (共通接線 2)
  if(eq(c1.r - c2.r, d)) return 1;// 内接する (共通接線 1)
  return 0;// 内包する (共通接線 0)
}

Real distance(const Point &a, const Point &b) {
  return abs(a - b);
}

Real distance(const Line &l, const Point &p) {
  return abs(p - projection(l, p));
}

Real distance(const Line &l, const Line &m) {
  return intersect(l, m) ? 0 : distance(l, m.a);
}

Real distance(const Segment &s, const Point &p) {
  Point r = projection(s, p);
  if(intersect(s, r)) return abs(r - p);
  return min(abs(s.a - p), abs(s.b - p));
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_2_D
Real distance(const Segment &a, const Segment &b) {
  if(intersect(a, b)) return 0;
  return min({distance(a, b.a), distance(a, b.b), distance(b, a.a), distance(b, a.b)});
}

Real distance(const Line &l, const Segment &s) {
  if(intersect(l, s)) return 0;
  return min(distance(l, s.a), distance(l, s.b));
}

// 交点
Point crosspoint(const Line &l, const Line &m) {
  Real A = cross(l.b - l.a, m.b - m.a);
  Real B = cross(l.b - l.a, l.b - m.a);
  if(eq(abs(A), 0.0) && eq(abs(B), 0.0)) return m.a;
  return m.a + (m.b - m.a) * B / A;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_2_C
Point crosspoint(const Segment &l, const Segment &m) {
  return crosspoint(Line(l), Line(m));
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_7_D
pair< Point, Point > crosspoint(const Circle &c, const Line l) {
  Point pr = projection(l, c.p);
  Point e = (l.b - l.a) / abs(l.b - l.a);
  if(eq(distance(l, c.p), c.r)) return {pr, pr};
  double base = sqrt(c.r * c.r - norm(pr - c.p));
  return {pr - e * base, pr + e * base};
}

pair< Point, Point > crosspoint(const Circle &c, const Segment &l) {
  Line aa = Line(l.a, l.b);
  if(intersect(c, l) == 2) return crosspoint(c, aa);
  auto ret = crosspoint(c, aa);
  if(dot(l.a - ret.first, l.b - ret.first) < 0) ret.second = ret.first;
  else ret.first = ret.second;
  return ret;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_7_E
pair< Point, Point > crosspoint(const Circle &c1, const Circle &c2) {
  Real d = abs(c1.p - c2.p);
  Real a = acos((c1.r * c1.r + d * d - c2.r * c2.r) / (2 * c1.r * d));
  Real t = atan2(c2.p.imag() - c1.p.imag(), c2.p.real() - c1.p.real());
  Point p1 = c1.p + Point(cos(t + a) * c1.r, sin(t + a) * c1.r);
  Point p2 = c1.p + Point(cos(t - a) * c1.r, sin(t - a) * c1.r);
  return {p1, p2};
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_7_F
// 点 p を通る円 c の接線
pair< Point, Point > tangent(const Circle &c1, const Point &p2) {
  return crosspoint(c1, Circle(p2, sqrt(norm(c1.p - p2) - c1.r * c1.r)));
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_7_G
// 円 c1, c2 の共通接線
Lines tangent(Circle c1, Circle c2) {
  Lines ret;
  if(c1.r < c2.r) swap(c1, c2);
  Real g = norm(c1.p - c2.p);
  if(eq(g, 0)) return ret;
  Point u = (c2.p - c1.p) / sqrt(g);
  Point v = rotate(PI * 0.5, u);
  for(int s : {-1, 1}) {
    Real h = (c1.r + s * c2.r) / sqrt(g);
    if(eq(1 - h * h, 0)) {
      ret.emplace_back(c1.p + u * c1.r, c1.p + (u + v) * c1.r);
    } else if(1 - h * h > 0) {
      Point uu = u * h, vv = v * sqrt(1 - h * h);
      ret.emplace_back(c1.p + (uu + vv) * c1.r, c2.p - (uu + vv) * c2.r * s);
      ret.emplace_back(c1.p + (uu - vv) * c1.r, c2.p - (uu - vv) * c2.r * s);
    }
  }
  return ret;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_3_B
// 凸性判定
bool is_convex(const Polygon &p) {
  int n = (int) p.size();
  for(int i = 0; i < n; i++) {
    if(ccw(p[(i + n - 1) % n], p[i], p[(i + 1) % n]) == -1) return false;
  }
  return true;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_4_A
// 凸包
// 頂点が全て整数のときはEPS=0にしないとバグる
Polygon convex_hull(Polygon &p) {
  int n = (int) p.size(), k = 0;
  if(n <= 2) return p;
  sort(p.begin(), p.end());
  vector< Point > ch(2 * n);
  for(int i = 0; i < n; ch[k++] = p[i++]) {
    while(k >= 2 && cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < EPS) --k;
  }
  for(int i = n - 2, t = k + 1; i >= 0; ch[k++] = p[i--]) {
    while(k >= t && cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < EPS) --k;
  }
  ch.resize(k - 1);
  return ch;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_3_C
// 多角形と点の包含判定
enum {
  OUT, ON, IN
};

int contains(const Polygon &Q, const Point &p) {
  bool in = false;
  for(int i = 0; i < Q.size(); i++) {
    Point a = Q[i] - p, b = Q[(i + 1) % Q.size()] - p;
    if(a.imag() > b.imag()) swap(a, b);
    if(a.imag() <= 0 && 0 < b.imag() && cross(a, b) < 0) in = !in;
    if(cross(a, b) == 0 && dot(a, b) <= 0) return ON;
  }
  return in ? IN : OUT;
}

int contains(const Circle &Q, const Point &p){
  Real left=(p.real()-Q.p.real())*(p.real()-Q.p.real())+(p.imag()-Q.p.imag())*(p.imag()-Q.p.imag());
  Real right=Q.r*Q.r;
  if(fabs(left-right)<EPS){
    return ON;
  }else if(left<right){
    return IN;
  }else{
    return OUT;
  }
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1033
// 線分の重複除去
void merge_segments(vector< Segment > &segs) {

  auto merge_if_able = [](Segment &s1, const Segment &s2) {
    if(abs(cross(s1.b - s1.a, s2.b - s2.a)) > EPS) return false;
    if(ccw(s1.a, s2.a, s1.b) == 1 || ccw(s1.a, s2.a, s1.b) == -1) return false;
    if(ccw(s1.a, s1.b, s2.a) == -2 || ccw(s2.a, s2.b, s1.a) == -2) return false;
    s1 = Segment(min(s1.a, s2.a), max(s1.b, s2.b));
    return true;
  };

  for(int i = 0; i < segs.size(); i++) {
    if(segs[i].b < segs[i].a) swap(segs[i].a, segs[i].b);
  }
  for(int i = 0; i < segs.size(); i++) {
    for(int j = i + 1; j < segs.size(); j++) {
      if(merge_if_able(segs[i], segs[j])) {
        segs[j--] = segs.back(), segs.pop_back();
      }
    }
  }
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=1033
// 線分アレンジメント
// 任意の2線分の交点を頂点としたグラフを構築する
vector< vector< int > > segment_arrangement(vector< Segment > &segs, vector< Point > &ps) {
  vector< vector< int > > g;
  int N = (int) segs.size();
  for(int i = 0; i < N; i++) {
    ps.emplace_back(segs[i].a);
    ps.emplace_back(segs[i].b);
    for(int j = i + 1; j < N; j++) {
      const Point p1 = segs[i].b - segs[i].a;
      const Point p2 = segs[j].b - segs[j].a;
      if(cross(p1, p2) == 0) continue;
      if(intersect(segs[i], segs[j])) {
        ps.emplace_back(crosspoint(segs[i], segs[j]));
      }
    }
  }
  sort(begin(ps), end(ps));
  ps.erase(unique(begin(ps), end(ps)), end(ps));

  int M = (int) ps.size();
  g.resize(M);
  for(int i = 0; i < N; i++) {
    vector< int > vec;
    for(int j = 0; j < M; j++) {
      if(intersect(segs[i], ps[j])) {
        vec.emplace_back(j);
      }
    }
    for(int j = 1; j < vec.size(); j++) {
      g[vec[j - 1]].push_back(vec[j]);
      g[vec[j]].push_back(vec[j - 1]);
    }
  }
  return (g);
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_4_C
// 凸多角形の切断
// 直線 l.a-l.b で切断しその左側にできる凸多角形を返す
Polygon convex_cut(const Polygon &U, Line l) {
  Polygon ret;
  for(int i = 0; i < U.size(); i++) {
    Point now = U[i], nxt = U[(i + 1) % U.size()];
    if(ccw(l.a, l.b, now) != -1) ret.push_back(now);
    if(ccw(l.a, l.b, now) * ccw(l.a, l.b, nxt) < 0) {
      ret.push_back(crosspoint(Line(now, nxt), l));
    }
  }
  return (ret);
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_3_A
// 多角形の面積
Real area(const Polygon &p) {
  Real A = 0;
  for(int i = 0; i < p.size(); ++i) {
    A += cross(p[i], p[(i + 1) % p.size()]);
  }
  return A * 0.5;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_7_H
// 円と多角形の共通部分の面積
Real area(const Polygon &p, const Circle &c) {
  if(p.size() < 3) return 0.0;
  function< Real(Circle, Point, Point) > cross_area = [&](const Circle &c, const Point &a, const Point &b) {
    Point va = c.p - a, vb = c.p - b;
    Real f = cross(va, vb), ret = 0.0;
    if(eq(f, 0.0)) return ret;
    if(max(abs(va), abs(vb)) < c.r + EPS) return f;
    if(distance(Segment(a, b), c.p) > c.r - EPS) return c.r * c.r * arg(vb * conj(va));
    auto u = crosspoint(c, Segment(a, b));
    vector< Point > tot{a, u.first, u.second, b};
    for(int i = 0; i + 1 < tot.size(); i++) {
      ret += cross_area(c, tot[i], tot[i + 1]);
    }
    return ret;
  };
  Real A = 0;
  for(int i = 0; i < p.size(); i++) {
    A += cross_area(c, p[i], p[(i + 1) % p.size()]);
  }
  return A*0.5;
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_4_B
// 凸多角形の直径(最遠頂点対間距離)
Real convex_diameter(const Polygon &p) {
  int N = (int) p.size();
  int is = 0, js = 0;
  for(int i = 1; i < N; i++) {
    if(p[i].imag() > p[is].imag()) is = i;
    if(p[i].imag() < p[js].imag()) js = i;
  }
  Real maxdis = norm(p[is] - p[js]);

  int maxi, maxj, i, j;
  i = maxi = is;
  j = maxj = js;
  do {
    if(cross(p[(i + 1) % N] - p[i], p[(j + 1) % N] - p[j]) >= 0) {
      j = (j + 1) % N;
    } else {
      i = (i + 1) % N;
    }
    if(norm(p[i] - p[j]) > maxdis) {
      maxdis = norm(p[i] - p[j]);
      maxi = i;
      maxj = j;
    }
  } while(i != is || j != js);
  return sqrt(maxdis);
}

// http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=CGL_5_A
// 最近点対
Real closest_pair(Points ps) {
  if(ps.size() <= 1) throw (0);
  sort(begin(ps), end(ps));

  auto compare_y = [&](const Point &a, const Point &b) {
    return imag(a) < imag(b);
  };
  vector< Point > beet(ps.size());
  const Real INF = 1e18;

  function< Real(int, int) > rec = [&](int left, int right) {
    if(right - left <= 1) return INF;
    int mid = (left + right) >> 1;
    auto x = real(ps[mid]);
    auto ret = min(rec(left, mid), rec(mid, right));
    inplace_merge(begin(ps) + left, begin(ps) + mid, begin(ps) + right, compare_y);
    int ptr = 0;
    for(int i = left; i < right; i++) {
      if(abs(real(ps[i]) - x) >= ret) continue;
      for(int j = 0; j < ptr; j++) {
        auto luz = ps[i] - beet[ptr - j - 1];
        if(imag(luz) >= ret) break;
        ret = min(ret, abs(luz));
      }
      beet[ptr++] = ps[i];
    }
    return ret;
  };
  return rec(0, (int) ps.size());
}

namespace atcoder {

// Implement (union by size) + (path compression)
// Reference:
// Zvi Galil and Giuseppe F. Italiano,
// Data structures and algorithms for disjoint set union problems
struct dsu {
  public:
    dsu() : _n(0) {}
    explicit dsu(int n) : _n(n), parent_or_size(n, -1) {}

    int merge(int a, int b) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        int x = leader(a), y = leader(b);
        if (x == y) return x;
        if (-parent_or_size[x] < -parent_or_size[y]) std::swap(x, y);
        parent_or_size[x] += parent_or_size[y];
        parent_or_size[y] = x;
        return x;
    }

    bool same(int a, int b) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        return leader(a) == leader(b);
    }

    int leader(int a) {
        assert(0 <= a && a < _n);
        if (parent_or_size[a] < 0) return a;
        return parent_or_size[a] = leader(parent_or_size[a]);
    }

    int size(int a) {
        assert(0 <= a && a < _n);
        return -parent_or_size[leader(a)];
    }

    std::vector<std::vector<int>> groups() {
        std::vector<int> leader_buf(_n), group_size(_n);
        for (int i = 0; i < _n; i++) {
            leader_buf[i] = leader(i);
            group_size[leader_buf[i]]++;
        }
        std::vector<std::vector<int>> result(_n);
        for (int i = 0; i < _n; i++) {
            result[i].reserve(group_size[i]);
        }
        for (int i = 0; i < _n; i++) {
            result[leader_buf[i]].push_back(i);
        }
        result.erase(
            std::remove_if(result.begin(), result.end(),
                           [&](const std::vector<int>& v) { return v.empty(); }),
            result.end());
        return result;
    }

  private:
    int _n;
    // root node: -1 * component size
    // otherwise: parent
    std::vector<int> parent_or_size;
};

}  // namespace atcoder

const vector<pair<ll,ll>> star_lines={{0,2},{0,3},{1,3},{1,4},{2,4}};

#include <vector>
#include <utility>
#include <queue>
#include <algorithm>
struct Dijkstra{
  private:
    double INF=10000000000000LL;
    std::vector<std::vector<std::pair<long long,double>>> G;
    int V;
    inline void _add(long long s,long long t,double c){
      G[s].emplace_back(std::make_pair(t,c));
    }
  public:
    Dijkstra(int N):V(N) {
      G.resize(V);
    }
    void add1(long long s,long long t,double c){
      _add(s,t,c);
    }
    void add2(long long s,long long t,double c){
      _add(s,t,c);
      _add(t,s,c);
    }
    std::vector<double> run(long long s){
      std::vector<double> res(V,INF);
      std::priority_queue<std::pair<double,long long>,std::vector<std::pair<double,long long>>,std::greater<std::pair<double,long long>>> que;
      res[s]=0;
      que.push(std::make_pair(0,s));
      while(!que.empty()){
        std::pair<double,long long> p=que.top();
        que.pop();
        long long v=p.second;
        if(res[v]<p.first){
          continue;
        }
        for(const std::pair<long long,double>& e:G[v]){
          if(res[e.first]>res[v]+e.second){
            res[e.first]=res[v]+e.second;
            que.push(std::make_pair(res[e.first],e.first));
          }
        }
      }
      return res;
    }
};

struct Star{
  Polygon pol;
  ll cx,cy;
  Segments segm;
  Star(){
    pol.resize(5);
    segm.resize(5);
  }
  Star(ll x,ll y,ll a,ll r){
    pol.resize(5);
    cx=x;
    cy=y;
    vector<Point> tmp(5);
    tmp[0]=Point(0,r);
    tmp[0]=rotate(degree_to_radian(a),tmp[0]);
    for(int i=0;i<4;i++){
      tmp[i+1]=rotate(degree_to_radian(72),tmp[i]);
    }
    for(int i=0;i<5;i++){
      pol[i]=Point(tmp[i].real()+cx,tmp[i].imag()+cy);
    }
    segm.resize(5);
    for(ll i=0;i<5;i++){
      segm[i]=Segment(pol[star_lines[i].first],pol[star_lines[i].second]);
    }
  }
};

struct Union{
  vector<Point> ps;
  Segments segms;
};

void solve(ll N,ll M,ll L){
  vector<Star> S(N);
  for(ll i=0;i<N;i++){
    ll x,y,a,r;
    cin>>x>>y>>a>>r;
    S[i]=Star(x,y,a,r);
  }
  atcoder::dsu uf(N);
  for(ll i=0;i<N;i++){
    for(ll j=i+1;j<N;j++){
      ll f=0;
      for(ll x=0;x<5;x++){
        for(ll y=0;y<5;y++){
          if(intersect(S[i].segm[x],S[j].segm[y])){
            f=1;
            break;
          }
        }
        if(f){
          break;
        }
      }
      if(f){
        uf.merge(i,j);
      }
    }
  }
  vector<vector<int>> grps=uf.groups();
  vector<Union> uni(grps.size());
  ll start,end;
  for(unsigned i=0;i<grps.size();i++){
    for(int j:grps[i]){
      if(j==M-1){
        start=i;
      }
      if(j==L-1){
        end=i;
      }
      for(ll k=0;k<5;k++){
        uni[i].ps.push_back(S[j].pol[k]);
        uni[i].segms.push_back(S[j].segm[k]);
      }
    }
  }
  ll B=uni.size();
  //cerr<<"uni_size: "<<B<<endl;
  //cerr<<start<<" "<<end<<endl;
  Dijkstra ijk(B);
  for(ll i=0;i<B;i++){
    for(ll j=0;j<B;j++){
      if(i==j){
        continue;
      }
      for(Point x:uni[i].ps){
        for(Segment y:uni[j].segms){
          ijk.add2(i,j,distance(y,x));
          //cerr<<i<<" "<<j<<" "<<double_out(10)<<distance(y,x)<<endl;
        }
      }
    }
  }
  cout<<double_out(10)<<ijk.run(start)[end]<<endl;
}


int main(){
  ll N,M,L;
  while(1){
    cin>>N>>M>>L;
    if(N==0){
      break;
    }
    solve(N,M,L);
  }
}
accuracy: 1
time_ms: 190
memory_kb: 14400
final_rank: 17

code_snippet: #include <bits/stdc++.h>
using namespace std;

// はじめに
using Double = double;
const Double EPS = 1e-8;
const Double PI = acos(-1);
inline int sign(const Double &x) { return x <= -EPS ? -1 : (x >= EPS ? 1 : 0); }
inline bool equals(const Double &a, const Double &b) { return sign(a - b) == 0; }
Double radian_to_degree(Double r) { return r * 180.0 / PI; }
Double degree_to_radian(Double d) { return d * PI / 180.0; }

// 点
using Point = complex<Double>;
// Point operator*(const Point &p, const Double &d) { return Point(p.real() * d, p.imag() * d); }  // 定数を掛ける
// Point operator/(const Point &p, const Double &d) { return Point(p.real() / d, p.imag() / d); }  // 定数で割る
istream &operator>>(istream &is, Point &p) {
    Double x, y;
    is >> x >> y;
    p = Point(x, y);
    return is;
}
Double dot(const Point &a, const Point &b) { return a.real() * b.real() + a.imag() * b.imag(); }    // 内積
Double cross(const Point &a, const Point &b) { return a.real() * b.imag() - a.imag() * b.real(); }  // 外積
// thetaだけ反時計回りに回転させる(thetaは弧度法なので、例えば90度回転させたいときにはtheta=PI/2とする)
Point rotate(const Point &p, const Double &theta) { return p * Point(cos(theta), sin(theta)); }

// 直線
struct Line {
    Point a, b;
    Line() = default;
    Line(Point a, Point b) : a(a), b(b) {}
    friend ostream &operator<<(ostream &os, const Line &p) { return os << p.a << "->" << p.b; }
    friend istream &operator>>(istream &is, Line &p) { return is >> p.a >> p.b; }
};

// 線分
struct Segment : Line {
    Segment() = default;
    Segment(Point a, Point b) : Line(a, b) {}
};

// 円
struct Circle {
    Point o;
    Double r;
    Circle() = default;
    Circle(Point o, Double r) : o(o), r(r) {}
    friend ostream &operator<<(ostream &os, const Circle &c) { return os << c.o << " " << c.r; }
    friend istream &operator>>(istream &is, Circle &c) { return is >> c.o >> c.r; }  // (x,y),rの順
};

// 射影(projection):CGL_1_A
// 直線lに点pから引いた垂線の足を求める
Point projection(const Line &l, const Point &p) {
    Double t = dot(p - l.a, l.b - l.a) / norm(l.b - l.a);
    return l.a + t * (l.b - l.a);
}

// 反射(reflection):CGL_1_B
// 直線lについて点pと線対称の点を求める
Point reflection(const Line &l, const Point &p) { return p + (projection(l, p) - p) * (Double)2.0; }

// 反時計回り(ccw):CGL_1_C
// 点a,b,cの位置関係を調べる(aを基準とする)
constexpr int COUNTER_CLOCKWISE = 1;  // a-b-cが反時計回り
constexpr int CLOCKWISE = -1;         // a-b-cが時計回り
constexpr int ONLINE_BACK = 2;        // c-a-b(一直線上)
constexpr int ONLINE_FRONT = -2;      // a-b-c(一直線上)
constexpr int ON_SEGMENT = 0;         // a-c-b(一直線上)
int ccw(const Point &a, Point b, Point c) {
    b = b - a, c = c - a;
    if (sign(cross(b, c)) == 1) return COUNTER_CLOCKWISE;
    if (sign(cross(b, c)) == -1) return CLOCKWISE;
    if (sign(dot(b, c)) == -1) return ONLINE_BACK;
    if (norm(b) < norm(c)) return ONLINE_FRONT;
    return ON_SEGMENT;
}

// 平行・垂直(parallel/orthogonal):CGL_2_A
// 2直線l1,l2が平行・垂直か判定する(それぞれ外積・内積が0)
bool is_parallel(const Line &l1, const Line &l2) { return sign(cross(l1.b - l1.a, l2.b - l2.a)) == 0; }
bool is_orthogonal(const Line &l1, const Line &l2) { return sign(dot(l1.b - l1.a, l2.b - l2.a)) == 0; }

// 交差判定(intersection):CGL_2_B
// 2線分s1,s2が交差するか判定する
bool intersection_ss(const Segment &s1, const Segment &s2) { return (ccw(s1.a, s1.b, s2.a) * ccw(s1.a, s1.b, s2.b) <= 0 && ccw(s2.a, s2.b, s1.a) * ccw(s2.a, s2.b, s1.b) <= 0); }
// 線分sと点pの場合(一応distance_spの実装でverify)
bool intersection_sp(const Segment &s, const Point &p) { return ccw(s.a, s.b, p) == ON_SEGMENT; }
// 直線lと点pの場合(absが1でない<=>ccwがONLINE_BACKかONLINE_FRONTかON_SEGMENT)(未verify)
bool intersection_lp(const Line &l, const Point &p) {
    int res = ccw(l.a, l.b, p);
    return (res == ONLINE_BACK || res == ONLINE_FRONT || res == ON_SEGMENT);
}
// 直線l1,l2の場合(未verify)
bool intersection_ll(const Line &l1, const Line &l2) {
    // cross_point_llの説明を参考に
    Point base = l1.b - l1.a;
    Double d12 = cross(base, l2.b - l2.a);
    Double d1 = cross(base, l1.b - l2.a);
    if (sign(d12) == 0 && sign(d1) == 0) return true;  // 一致
    // 平行か1点で交わるか
    return !is_parallel(l1, l2);
}

// 線分の交点(cross point):CGL_2_C
// 2直線l1,l2の交点を求める(未verify)
Point cross_point_ll(const Line &l1, const Line &l2) {
    // 2直線の関係:1点で交わるor平行or一致
    Point base = l1.b - l1.a;
    Double d12 = cross(base, l2.b - l2.a);
    Double d1 = cross(base, l1.b - l2.a);
    // 一致(sign(d12) == 0 は is_parallelと本質的に同じ, sign(d1) == 0は重なっているか)
    if (sign(d12) == 0 && sign(d1) == 0) return l2.a;  // 2直線が一致するので適当に点を返す
    assert(is_parallel(l1, l2) == false);              // assert(sign(d12) != 0);と同じ
    return l2.a + (l2.b - l2.a) * (d1 / d12);
}
// 2線分s1,s2の交点を求める
Point cross_point_ss(const Segment &s1, const Segment &s2) {
    assert(intersection_ss(s1, s2));  // 交点を持つかまず判定する
    // 1点で交わるor無数の点で交わる
    Point base = s1.b - s1.a;
    Double d12 = cross(base, s2.b - s2.a);
    Double d1 = cross(base, s1.b - s2.a);
    if (sign(d12) == 0 && sign(d1) == 0) {
        // 無数の点で交わる(未verify)
        if (intersection_sp(s1, s2.a)) return s2.a;
        if (intersection_sp(s1, s2.b)) return s2.b;
        if (intersection_sp(s2, s1.a)) return s1.a;
        assert(intersection_sp(s2, s1.b));
        return s1.b;
    }
    return s2.a + (s2.b - s2.a) * (d1 / d12);
}

// 距離(distance):CGL_2_D
// 2点a,bの距離を求める(未verify)
Double distance_pp(const Point &a, const Point &b) { return abs(a - b); }
// 直線lと点pの距離を求める(未verify)
Double distance_lp(const Line &l, const Point &p) { return abs(p - projection(l, p)); }
// 線分sと点pの距離を求める
Double distance_sp(const Segment &s, const Point &p) {
    Point r = projection(s, p);
    if (intersection_sp(s, r)) return abs(r - p);
    return min(abs(s.a - p), abs(s.b - p));
}
// 線分s1,s2の距離を求める
Double distance_ss(const Segment &s1, const Segment &s2) {
    if (intersection_ss(s1, s2)) return 0.0;
    return min({distance_sp(s1, s2.a), distance_sp(s1, s2.b), distance_sp(s2, s1.a), distance_sp(s2, s1.b)});
}

using ll = long long;
#define rep(i, n) for (int i = 0; i < (n); i++)
#define ALL(x) x.begin(), x.end()

struct star5 {
    vector<Segment> Segs;
    Double x, y, deg, r;
    star5() {}
    star5(Double x, Double y, Double deg, Double r) : x(x), y(y), deg(deg), r(r) {
        Point a1 = polar(r, PI / 2 + degree_to_radian(deg));
        Point p = Point(x, y);
        for (int i = 0; i < 5; i++) {
            Point a2 = rotate(a1, degree_to_radian(144));
            Segs.push_back(Segment(a1 + p, a2 + p));
            a1 = a2;
        }
    }
};

Double distance_star5(const star5 &s1, const star5 &s2) {
    Double ret = numeric_limits<Double>::infinity();
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            ret = min(ret, distance_ss(s1.Segs[i], s2.Segs[j]));
        }
    }
    return ret;
}

const Double INF = numeric_limits<Double>::infinity();

void solve(int N, int M, int L) {
    M--, L--;
    vector<star5> S(N);
    rep(i, N) {
        Double x, y, a, r;
        cin >> x >> y >> a >> r;
        S[i] = star5(x, y, a, r);
    }
    vector<vector<Double>> dist(N, vector<Double>(N, INF));
    rep(i, N) rep(j, N) dist[i][j] = distance_star5(S[i], S[j]);
    rep(k, N) rep(i, N) rep(j, N) dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
    cout << fixed << setprecision(15) << dist[M][L] << '\n';
    return;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int N, M, L;
    while (cin >> N >> M >> L, N) solve(N, M, L);
    return 0;
}
accuracy: 1
time_ms: 730
memory_kb: 4060
final_rank: 15

code_snippet: #include <bits/stdc++.h>
using namespace std;
struct Point{
    double x;
    double y;
    
    void rot(double theta){
        theta = theta / 180 * M_PI;
        double nx = x * cos(theta) - y * sin(theta);
        double ny = y * cos(theta) + x * sin(theta);
        x = nx;
        y = ny;
    }
};

int n, m, l;

double cross3(Point &p, Point &a, Point &b){
    return (a.x - p.x) * (b.y - p.y) - (a.y - p.y) * (b.x - p.x);
}

bool iscross(Point &a1, Point &a2, Point &b1, Point &b2){
    bool flg1 = cross3(a1, a2, b1) * cross3(a1, a2, b2) < 0;
    bool flg2 = cross3(b1, b2, a1) * cross3(b1, b2, a2) < 0;
    return flg1 & flg2;
}

double dot_dist(Point &a, Point &b){
    double dx = b.x - a.x;
    double dy = b.y - a.y;
    return sqrt(dx * dx + dy * dy);
}

double dot_line_dist(Point &p, Point &a, Point &b){
    Point d0 = {b.x - a.x, b.y - a.y};
    Point d1 = {p.x - a.x, p.y - a.y};
    double dot = d0.x * d1.x + d0.y * d1.y;
    double dd = d0.x * d0.x + d0.y * d0.y;
    if(0 <= dot && dot <= dd){
        double cross = d0.x * d1.y - d0.y * d1.x;
        return abs(cross) / sqrt(dd);
    }
    else return min(dot_dist(p, a), dot_dist(p, b));
}

double line_dist(Point &a1, Point &a2, Point &b1, Point &b2){
    if(iscross(a1, a2, b1, b2)) return 0;
    double ret = dot_line_dist(a1, b1, b2);
    ret = min(ret, dot_line_dist(a2, b1, b2));
    ret = min(ret, dot_line_dist(b1, a1, a2));
    ret = min(ret, dot_line_dist(b2, a1, a2));
    return ret;
}

void solve(){
    vector<vector<Point>> star(n, vector<Point>(5));
    double x, y, a, r;
    for(int i = 0; i < n; i++){
        cin >> x >> y >> a >> r;
        Point v = {0, r};
        v.rot(a);
        for(int j = 0; j < 5; j++){
            star[i][j] = {v.x + x, v.y + y};
            v.rot(144);
        }
    }
    vector<vector<double>> dist(n, vector<double>(n, 0));
    for(int i = 0; i < n; i++) for(int j = 0; j < n; j++){
        if(i == j) continue;
        double min_ = 1 << 30;
        for(int k = 0; k < 5; k++) for(int o = 0; o < 5; o++){
            double tmp = line_dist(star[i][k], star[i][(k + 1) % 5], star[j][o], star[j][(o + 1) % 5]);
            min_ = min(min_, tmp);
        }
        dist[i][j] = min_;
        dist[j][i] = min_;
    }
    for(int k = 0; k < n; k++) for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++) dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
    }
    cout << dist[m][l] << "\n";
}

int main(void){
    cout << fixed << setprecision(20);
    while(1){
        cin >> n >> m >> l;
        if(n == 0) break;
        m--; l--;
        solve();
    }
    
}
accuracy: 1
time_ms: 90
memory_kb: 3892
final_rank: 3

code_snippet: #line 1 "main.cpp"
#include <algorithm>
#include <cassert>
#include <cfloat>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <deque>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

using namespace std;

using lint  = int64_t;
using uint  = uint32_t;
using ulint = uint64_t;

template <class T>
using vector2d = vector<vector<T>>;

template <class T>
bool UpdateMax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return true;
  } else {
    return false;
  }
}

template <class T>
bool UpdateMin(T &a, const T &b) {
  if (a > b) {
    a = b;
    return true;
  } else {
    return false;
  }
}

template <class T>
void OutVec(const vector<T> &vec) {
  for (int i = 0; i < vec.size() - 1; ++i) {
    cout << vec[i] << " ";
  }
  cout << vec.back() << endl;
}

template <class T>
void OutVec2d(const vector2d<T> &vec) {
  for (auto v : vec) {
    OutVec(v);
  }
}

#line 2 "/home/arc/project/comppro/cplib/geometry/crosspoint.cpp"

#line 4 "/home/arc/project/comppro/cplib/geometry/crosspoint.cpp"

#line 2 "/home/arc/project/comppro/cplib/geometry/circle.cpp"

#include <stdexcept>

#line 2 "/home/arc/project/comppro/cplib/geometry/base.cpp"

namespace geometry {

const double EPS = 1e-8;
/**
 * @brief double比較用関数
 *
 * @param a
 * @param b
 * @return int a>bなら1, a<bなら-1, a=bなら0
 */
int doubleComp(double a, double b) {
  if (a > b + EPS)
    return 1;
  else if (b > a + EPS) {
    return -1;
  }
  return 0;
}

bool equals(double a, double b) {
  return doubleComp(a, b)==0;
}
} // namespace geometry
#line 2 "/home/arc/project/comppro/cplib/geometry/distance.cpp"

#line 2 "/home/arc/project/comppro/cplib/geometry/line.cpp"

#line 2 "/home/arc/project/comppro/cplib/geometry/vector.cpp"

#include <complex>
#include <limits>

#line 7 "/home/arc/project/comppro/cplib/geometry/vector.cpp"

namespace geometry {
using Vector2 = std::complex<double>;

/**
 * @brief aとbの内積　|a||b|cosΘ　を求める　
 *
 * @param a
 * @param b
 * @return double
 */
double dot(const Vector2 &a, const Vector2 &b) {
  return a.real() * b.real() + a.imag() * b.imag();
}

/**
 * @brief aとbの外積　|a||b|sinΘ　を求める
 *
 * @param a
 * @param b
 * @return double
 */
double cross(const Vector2 &a, const Vector2 &b) {
  return a.real() * b.imag() - a.imag() * b.real();
}

/**
 * @brief pを反時計回りにrad回転したベクトルを求める
 *
 * @param p 元のベクトル
 * @param rad 回転角(単位ラジアン)
 * @return Vector2 回転後のベクトル
 */
Vector2 rotate(const Vector2 &p, const double rad) {
  double c = cos(rad), s = sin(rad);
  return Vector2(c * p.real() - s * p.imag(), s * p.real() + c * p.imag());
}

/**
 * @brief aを基準点としたb, cの位置関係を調べる
 *
 * @param a
 * @param b
 * @param c
 * @return int
 *  cがbの反時計回りの位置: 1
 *  時計回りの位置: -1
 *  c,a,bがこの順番で同一直線上: 2
 *  a,b,cがこの順番で同一直線上: -2
 *  cが線分ab上: 0
 */
int ccw(const Vector2 &a, Vector2 b, Vector2 c) {
  b -= a;
  c -= a;
  if (doubleComp(cross(b, c), 0) == 1) {
    return 1;
  }
  if (doubleComp(cross(b, c), 0) == -1) {
    return -1;
  }
  if (doubleComp(dot(b, c), 0) == -1) {
    return 2;
  }
  if (norm(b) < norm(c)) {
    return -2;
  }
  return 0;
}

bool isNaN(const Vector2 &a) {
  return std::isnan(a.imag()) || std::isnan(a.real());
}

Vector2 unitVector(const Vector2 &a) {
  return a / std::abs(a);
}

Vector2 normalVector(const Vector2 &a) {
  return a * Vector2(0, 1);
}

static double nan = std::numeric_limits<double>::quiet_NaN();
const Vector2 NaN = Vector2(nan, nan);
}  // namespace geometry
#line 5 "/home/arc/project/comppro/cplib/geometry/line.cpp"

namespace geometry {
struct Line {
  Vector2 a, b;
  Line() = default;
  Line(Vector2 _a, Vector2 _b) : a(_a), b(_b) {
  }

  /**
   * @brief ax+by=cの直線を作成
   *
   * @param a
   * @param b
   * @param c
   */
  Line(double _a, double _b, double _c) {
    if (equals(_a, 0)) {
      a = Vector2(0, _c / _b);
      b = Vector2(1, _c / _b);
    } else if (equals(_b, 0)) {
      a = Vector2(_c / _a, 0);
      b = Vector2(_c / _a, 1);
    } else {
      a = Vector2(0, _c / _b);
      b = Vector2(_c / _a, 0);
    }
  }

  Vector2 vector() const {
    return b - a;
  }
};

struct Segment : Line {
  Segment() = default;
  Segment(Vector2 a, Vector2 b) : Line(a, b) {
  }
};

/**
 * @brief 2直線が平行であるかを返します
 *
 * @param s
 * @param t
 * @return true
 * @return false
 */
bool isParallel(const Line &s, const Line &t) {
  return equals(0, cross(s.vector(), t.vector()));
}

/**
 * @brief 2直線が垂直であるかを返します
 *
 * @param s
 * @param t
 * @return true
 * @return false
 */
bool isOrthogonal(const Line &s, const Line &t) {
  return equals(0, dot(s.vector(), t.vector()));
}

/**
 * @brief 2線分が交差しているかを返します
 *
 * @param s
 * @param t
 * @return true
 * @return false
 */
bool hasIntersect(const Segment &s, const Segment &t) {
  return ccw(s.a, s.b, t.a) * ccw(s.a, s.b, t.b) <= 0 &&
         ccw(t.a, t.b, s.a) * ccw(t.a, t.b, s.b) <= 0;
}

}  // namespace geometry
#line 6 "/home/arc/project/comppro/cplib/geometry/distance.cpp"

namespace geometry {
double distance(const Vector2 &a, const Vector2 &b) {
  double x = a.real() - b.real();
  double y = a.imag() - b.imag();
  return sqrt(x * x + y * y);
}

/**
 * @brief 点と直線の距離を求めます
 *
 * @param p 点
 * @param l 直線
 * @return double 点と直線の距離
 */
double distance(const Vector2 &p, const Line &l) {
  return std::abs(cross(l.vector(), p - l.a) / abs(l.vector()));
}

/**
 * @brief 点と線分の距離を求めます
 * 
 * @param p 点
 * @param l 線分
 * @return double 点と線分の距離 
 */
double distance(const Vector2 &p, const Segment &l) {
  if (dot(l.b - l.a, p - l.a) < EPS) {
    return abs(p-l.a);
  }
  if (dot(l.a - l.b, p - l.b) < EPS) {
    return abs(p-l.b);
  }
  return std::abs(cross(l.vector(), p - l.a) / abs(l.vector()));
}

/**
 * @brief 線分間の最短距離を求めます
 * 
 * @param s 
 * @param t 
 * @return double 
 */
double distance(const Segment &s, const Segment &t) {
  if (hasIntersect(s, t)) {
    return (double)0;
  }
  double ans = distance(t.a, s);
  ans = std::min(ans, distance(t.b, s));
  ans = std::min(ans, distance(s.a, t));
  ans = std::min(ans, distance(s.b, t));
  return ans;
}

} // namespace geometry
#line 2 "/home/arc/project/comppro/cplib/geometry/polygon.cpp"

#line 5 "/home/arc/project/comppro/cplib/geometry/polygon.cpp"

#line 7 "/home/arc/project/comppro/cplib/geometry/polygon.cpp"
namespace geometry {

using Polygon = std::vector<Vector2>;

/**
 * @brief 多角形の面積を求めます
 *
 * @param p 多角形の頂点を反時計回りに並べたもの
 * @return double
 */
double area(const Polygon &p) {
  double res = 0;
  int n      = p.size();
  for (int i = 0; i < n - 1; i++) {
    res += cross(p[i], p[i + 1]);
  }
  res += cross(p[n - 1], p[0]);
  return res * 0.5;
}

/**
 * @brief 多角形が凸かどうか判定します
 *
 * @param p 多角形の頂点を反時計回りに並べたもの
 * @return true 凸である場合
 * @return false 凸でない場合
 */
bool isConvex(const Polygon &p) {
  int n = p.size();
  int now, pre, nxt;
  for (int i = 0; i < n; i++) {
    pre = (i - 1 + n) % n;
    nxt = (i + 1) % n;
    now = i;
    if (ccw(p[pre], p[now], p[nxt]) == -1) {
      return false;
    }
  }
  return true;
}

/**
 * @brief 多角形に点が含まれているか判定します
 *
 * @param g 多角形の頂点を反時計回りに並べたもの
 * @param p 対象の点
 * @return int 含まれるなら2, 辺上にあるなら1, それ以外なら0
 */
int isContained(const Polygon &g, const Vector2 &p) {
  bool in = false;
  int n   = (int)g.size();
  for (int i = 0; i < n; i++) {
    Vector2 a = g[i] - p, b = g[(i + 1) % n] - p;
    if (imag(a) > imag(b)) {
      swap(a, b);
    }
    if (imag(a) <= EPS && EPS < imag(b) && cross(a, b) < -EPS) {
      in = !in;
    }
    if (equals(cross(a, b), 0) && doubleComp(dot(a, b), 0) <= 0) {
      return 1;
    }
  }
  return (in ? 2 : 0);
}

/**
 * @brief 凸包を求めます (O(N log N))
 *
 * @param p 多角形の頂点を反時計回りに並べたもの
 * @return Polygon pの凸包
 */
Polygon convexHull(Polygon &p) {
  int n = (int)p.size(), k = 0;
  std::sort(p.begin(), p.end(), [](const Vector2 &a, const Vector2 &b) {
    return (!equals(a.real(), b.real()) ? a.real() < b.real()
                                        : a.imag() < b.imag());
  });
  Polygon ch(2 * n);
  // 一直線上の3点を含める -> (< -EPS)
  // 含めない -> (< EPS)
  for (int i = 0; i < n; ch[k++] = p[i++]) {  // lower
    while (k >= 2 && cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < EPS) --k;
  }
  for (int i = n - 2, t = k + 1; i >= 0; ch[k++] = p[i--]) {  // upper
    while (k >= t && cross(ch[k - 1] - ch[k - 2], p[i] - ch[k - 1]) < EPS) --k;
  }
  ch.resize(k - 1);
  return ch;
}

}  // namespace geometry
#line 2 "/home/arc/project/comppro/cplib/geometry/proj_ref.cpp"

#line 4 "/home/arc/project/comppro/cplib/geometry/proj_ref.cpp"

#line 7 "/home/arc/project/comppro/cplib/geometry/proj_ref.cpp"

namespace geometry {
/**
 * @brief pからlに降ろした垂線の足を求める
 *
 * @param l
 * @param p
 * @return Vector2
 */
Vector2 projection(const Line &l, const Vector2 &p) {
  double t = dot(p - l.a, l.a - l.b) / std::norm(l.a - l.b);
  return l.a + (l.a - l.b) * t;
}
/**
 * @brief lを対称軸としてpと線対称の位置にある点を求める
 *
 * @param l
 * @param p
 * @return Vector2
 */
Vector2 reflection(const Line &l, const Vector2 &p) {
  return p + (projection(l, p) - p) * 2.0;
}
}  // namespace geometry
#line 10 "/home/arc/project/comppro/cplib/geometry/circle.cpp"

namespace geometry {
struct Circle {
  Vector2 c;
  double r;
  Circle() = default;
  Circle(Vector2 _c, double _r) : c(_c), r(_r) {
  }
};

/**
 * @brief 円の交差判定を行います
 *
 * @param c1
 * @param c2
 * @return int 2つの円が離れている: 4, 外接: 3, 2点交差: 2, 内接: 1, 内包: 0
 */
int checkIntersect(const Circle &c1, const Circle &c2) {
  double d = abs(c1.c - c2.c);
  if (d > c1.r + c2.r + EPS) {
    return 4;
  }
  // 外接している場合
  if (equals(d, c1.r + c2.r)) {
    return 3;
  }
  // 内接している場合
  if (equals(d, std::abs(c1.r - c2.r))) {
    return 1;
  }
  // 内包している場合
  if (d < std::abs(c1.r - c2.r) - EPS) {
    return 0;
  }
  return 2;
}

Circle incircle(const Vector2 &a, const Vector2 &b, const Vector2 &c) {
  double A = std::abs(b - c), B = std::abs(a - c), C = std::abs(a - b);
  Vector2 p(A * real(a) + B * real(b) + C * real(c),
            A * imag(a) + B * imag(b) + C * imag(c));
  p /= (A + B + C);
  double r = distance(p, Line(a, b));
  return Circle(p, r);
}

Circle incircle(const Polygon &p) {
  if (p.size() != 3) {
    throw std::invalid_argument("The size of p must be 3");
  }
  return incircle(p[0], p[1], p[2]);
}

}  // namespace geometry
#line 7 "/home/arc/project/comppro/cplib/geometry/crosspoint.cpp"

namespace geometry {
/**
 * @brief 円と直線の交点を求める
 *
 * @param c
 * @param l
 * @return std::vector<Vector2>
 */
std::vector<Vector2> crossPoint(const Circle &c, const Line &l) {
  std::vector<Vector2> res;
  double d = distance(c.c, l);
  // 交点を持たない
  if (d > c.r + EPS) {
    return res;
  }
  // 接する
  Vector2 h = projection(l, c.c);
  if (equals(d, c.r)) {
    res.emplace_back(h);
    return res;
  }
  Vector2 e = unitVector(l.b - l.a);
  double ph = sqrt(c.r * c.r - d * d);
  res.emplace_back(h - e * ph);
  res.emplace_back(h + e * ph);
  return res;
}
/**
 * @brief 2直線の交点を求める
 *
 * @param s
 * @param t
 * @return Vector2 2直線が平行であればNaN, そうでなければ交点
 */
Vector2 crossPoint(const Line &s, const Line &t) {
  if (isParallel(s, t)) {
    return NaN;
  }
  double d1 = cross(s.b - s.a, t.b - t.a);
  double d2 = cross(s.b - s.a, s.b - t.a);
  if (equals(std::abs(d1), 0) && equals(std::abs(d2), 0)) {
    return t.a;
  }
  return t.a + (t.b - t.a) * (d2 / d1);
}

/**
 * @brief 2線分の交点を求める
 *
 * @param s
 * @param t
 * @return Vector2 2線分が交点を持たなければNaN, そうでなければ交点
 */
Vector2 crossPoint(const Segment &s, const Segment &t) {
  if (!hasIntersect(s, t)) {
    return NaN;
  }
  return crossPoint(Line(s), Line(t));
}

/**
 * @brief 2つの円の交点を求める
 *
 * @param c1
 * @param c2
 * @return vector<Point>
 */
std::vector<Vector2> crossPoint(const Circle &c1, const Circle &c2) {
  std::vector<Vector2> res;
  int mode = checkIntersect(c1, c2);
  // 2つの中心の距離
  double d = abs(c1.c - c2.c);
  // 2円が離れている場合
  if (mode == 4) {
    return res;
  }
  // 1つの円がもう1つの円に内包されている場合
  if (mode == 0) {
    return res;
  }
  // 2円が外接する場合
  if (mode == 3) {
    double t = c1.r / (c1.r + c2.r);
    res.emplace_back(c1.c + (c2.c - c1.c) * t);
    return res;
  }
  // 内接している場合
  if (mode == 1) {
    if (c2.r < c1.r - EPS) {
      res.emplace_back(c1.c + (c2.c - c1.c) * (c1.r / d));
    } else {
      res.emplace_back(c2.c + (c1.c - c2.c) * (c2.r / d));
    }
    return res;
  }
  // 2円が重なる場合
  double rc1 = (c1.r * c1.r + d * d - c2.r * c2.r) / (2 * d);
  double rs1 = sqrt(c1.r * c1.r - rc1 * rc1);
  if (c1.r - abs(rc1) < EPS) {
    rs1 = 0;
  }
  Vector2 e12 = (c2.c - c1.c) / abs(c2.c - c1.c);
  res.emplace_back(c1.c + rc1 * e12 + rs1 * e12 * Vector2(0, 1));
  res.emplace_back(c1.c + rc1 * e12 + rs1 * e12 * Vector2(0, -1));
  return res;
}

}  // namespace geometry
#line 2 "/home/arc/project/comppro/cplib/geometry/util.cpp"

#line 4 "/home/arc/project/comppro/cplib/geometry/util.cpp"
namespace geometry {
/**
 * @brief ラジアンから度に変換
 * 
 * @param rad ラジアン
 * @return double 度
 */
double radToDeg(const double rad) { return rad * 180.0 / M_PI; }

/**
 * @brief 度からラジアンに変換
 * 
 * @param deg 度
 * @return double ラジアン
 */
double degToRad(const double deg) { return deg * M_PI / 180; }



} // namespace geometry
#line 2 "/home/arc/project/comppro/cplib/graph/dijkstra.cpp"

#line 4 "/home/arc/project/comppro/cplib/graph/dijkstra.cpp"

#line 2 "/home/arc/project/comppro/cplib/graph/graph.cpp"

#line 6 "/home/arc/project/comppro/cplib/graph/graph.cpp"

template<typename CostType>
/// \brief 辺の情報
struct Edge{
  int      to;
  CostType cost;

  Edge(int t, CostType c): to(t), cost(c){
  }
};

template<typename CostType>
class Graph{

private:

  /// \brief adjacent_list_[n]=ノードnの隣接リストを表すvector<Edge>
  std::vector<std::vector<Edge<CostType>>> adjacent_list_;

public:

  /// \brief 頂点数
  const int NODE_SIZE_;

  /// \brief コンストラクタ
  /// \param node_size 頂点数
  explicit Graph(const int node_size): NODE_SIZE_(node_size),
                                       adjacent_list_(node_size){
  }

  /// \brief 有向グラフの辺を生やす
  /// \param from 辺の根本の頂点の番号
  /// \param to 辺の先の頂点の番号
  /// \param cost 辺のコスト
  void SetDirectedEdge(const int from, const int to, const CostType cost){
    adjacent_list_[from].push_back(Edge<CostType>(to, cost));
  }

  /// \brief 無向グラフの辺を生やす
  /// \param node_a 一方の頂点の番号
  /// \param node_b もう一方の頂点の番号
  /// \param cost 辺のコスト
  void
  SetUndirectedEdge(const int node_a, const int node_b, const CostType cost){
    SetDirectedEdge(node_a, node_b, cost);
    SetDirectedEdge(node_b, node_a, cost);
  }

  /// \brief ある頂点の隣接リストを取得
  /// \param node_num 頂点の番号
  /// \return 隣接リスト
  std::vector<Edge<CostType>> GetAdjacentList(int node_num) const{
    return adjacent_list_[node_num];
  }
};





#line 6 "/home/arc/project/comppro/cplib/graph/dijkstra.cpp"

/// \brief ダイクストラ法により，ある頂点からの最短経路を求める
/// \tparam CostType 辺のコストの型
/// \param graph グラフ
/// \param start_node どの頂点から最短経路を求めるか
/// \param INF CostType型で十分大きい値
/// \param Identity 単位元(0など)
/// \return
/// 1要素目:
/// グラフの各頂点への最短経路を示す配列(到達不可能なときはINF)，負の辺が入っていた場合はsize0の配列
/// 2要素目: 各頂点の前の頂点を示す配列(開始点は-1)
/// 負の辺が入っていた場合はsize0の配列
template <typename CostType>
std::pair<std::vector<CostType>, std::vector<int>> Dijkstra(
    const Graph<CostType> &graph, const int start_node,
    CostType Identity, CostType INF) {
  struct Info {
    int node;
    CostType cost;

    Info(int n, CostType c) : node(n), cost(c){};

    bool operator>(const Info &another) const {
      return cost > another.cost;
    }
  };

  std::vector<CostType> min_cost(graph.NODE_SIZE_);
  std::vector<int> prev(graph.NODE_SIZE_, -1);
  std::priority_queue<Info, std::vector<Info>, std::greater<Info>> pq;
  for (int i = 0; i < min_cost.size(); i++) {
    min_cost[i] = (i == start_node) ? Identity : INF;
  }
  pq.push(Info(start_node, Identity));

  while (!pq.empty()) {
    Info current_info = pq.top();
    pq.pop();
    if (min_cost[current_info.node] < current_info.cost) {
      continue;
    }
    const std::vector<Edge<CostType>> &adjacency_list =
        graph.GetAdjacentList(current_info.node);
    for (auto e : adjacency_list) {
      if (min_cost[e.to] > min_cost[current_info.node] + e.cost) {
        min_cost[e.to] = min_cost[current_info.node] + e.cost;
        prev[e.to]     = current_info.node;
        pq.push(Info(e.to, min_cost[e.to]));
      }
    }
  }

  return {min_cost, prev};
}

/**
 * @brief 経路復元を行う
 *
 * @param prev ダイクストラで得た戻り値のprev
 * @param g 最終ノード
 * @return std::vector<int> 開始点からgまでの最短パス
 */
std::vector<int> getPath(const std::vector<int> &prev, int g) {
  std::vector<int> res;
  int curr = g;
  while (prev[curr] != -1) {
    res.push_back(curr);
    curr = prev[curr];
  }
  res.push_back(curr);
  std::reverse(res.begin(), res.end());
  return res;
}
#line 69 "main.cpp"

// 各線分ペアに対して: 交点を持っていればコスト0, そうでなければ4つの距離のmin で両端を結ぶ

using namespace geometry;
struct Star {
  vector<Vector2> points;
  Star(double x, double y, double a, double r) {
    points = vector<Vector2>(5);
    // 原点中心で☆を書く
    points[0] = rotate(Vector2(0, r), degToRad(a));
    for (int i = 1; i <= 4; i++) {
      points[i] = rotate(points[i-1], degToRad(144));
    }

    // 平行移動
    Vector2 move(x, y);
    for (auto &p : points) {
      p += move;
    }
  }
  Star() = default;
};

int getNode(int starId, int posId) {
  return starId * 5 + posId;
}

double solve(int n, int m, int L) {
  vector<Star> stars(n);
  for (int i = 0; i < n; i++) {
    int x, y, a, r;
    cin >> x >> y >> a >> r;
    stars[i] = Star(x, y, a, r);
  }

  Graph<double> graph(n * 5);
  for (int i = 0; i < stars.size(); i++) {
    for (int j = 0; j < 5; j++) {
      for (int k = j + 1; k < 5; k++) {
        graph.SetUndirectedEdge(getNode(i, j), getNode(i, k), 0);
      }
    }
  }

  for (int i = 0; i < stars.size(); i++) {
    for (int j = i + 1; j < stars.size(); j++) {
      for (int k = 0; k < 5; k++) {
        int siai    = k;
        int sibi    = (k + 1) % 5;
        Vector2 sia = stars[i].points[siai];
        Vector2 sib = stars[i].points[sibi];
        Segment segI(sia, sib);
        for (int l = 0; l < 5; l++) {
          int sjai    = l;
          int sjbi    = (l + 1) % 5;
          Vector2 sja = stars[j].points[sjai];
          Vector2 sjb = stars[j].points[sjbi];
          Segment segJ(sja, sjb);

          double cost;

          if (!hasIntersect(segI, segJ)) {
            // 4点距離
            cost = min({abs(sia - sja), abs(sia - sjb), abs(sib - sja),
                        abs(sib - sjb),
                        distance(sia, segJ),
                        distance(sib, segJ),
                        distance(sja, segI),
                        distance(sjb, segI)});

          } else {
            cost = 0;
          }

          graph.SetUndirectedEdge(getNode(i, siai), getNode(j, sjai), cost);
          graph.SetUndirectedEdge(getNode(i, sibi), getNode(j, sjai), cost);
          graph.SetUndirectedEdge(getNode(i, siai), getNode(j, sjbi), cost);
          graph.SetUndirectedEdge(getNode(i, sibi), getNode(j, sjbi), cost);
        }
      }
    }
  }

  auto [res, _] = Dijkstra(graph, getNode(m - 1, 0), 0.0,
                           numeric_limits<double>::infinity());

  return res[getNode(L - 1, 0)];
}

int main() {
  cout << std::fixed << std::setprecision(16);
  cin.tie(nullptr);
  std::ios::sync_with_stdio(false);

  // Segment seg(Vector2(0,0), Vector2(3,0)), seg2(Vector2(1,0), Vector2(2,0));
  // Vector2 cp = crossPoint(seg, seg2);
  // bool hasint = hasIntersect(seg, seg2);
  // cout<<cp.real()<<cp.imag()<<endl;
  // cout<<hasint<<endl;

  while (true) {
    int n, m, l;
    cin >> n >> m >> l;
    if (n == 0) {
      break;
    }
    cout << solve(n, m, l) << endl;
  }

  return 0;
}
accuracy: 1
time_ms: 320
memory_kb: 20368
final_rank: 18