#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static const double EPS = 1e-8; static const double PI = 4.0 * atan(1.0); static const double PI2 = 8.0 * atan(1.0); typedef long long ll; #define ALL(c) (c).begin(), (c).end() #define CLEAR(v) memset(v,0,sizeof(v)) #define MP(a,b) make_pair((a),(b)) #define REP(i,n) for(int i=0;i<(int)n;++i) typedef complex P; //点(x0,y0)を通り円(x1,y1,r)と接する直線の接点 void tangency(double x0, double y0, double x1, double y1, double r, P out[2]) { const double alpha = atan2(y1 - y0, x1 - x0); const double L = hypot(x1 - x0, y1 - y0); const double theta = asin(r / L); const double M = sqrt(L * L - r * r); out[0] = P(x0 + M * cos(alpha + theta), y0 + M * sin(alpha + theta)); out[1] = P(x0 + M * cos(alpha - theta), y0 + M * sin(alpha - theta)); } //円(x0,y0,r0)と円(x1,y1,r1)の共通接線の接点 void tangency(double x0, double y0, double r0, double x1, double y1, double r1, pair out[4]) { const P v(x1 - x0, y1 - y0); const double a0 = arg(v); const double a1 = acos((r0 - r1) / abs(v)); out[0] = make_pair(P(x0 + r0 * cos(a0 + a1), y0 + r0 * sin(a0 + a1)), P(x1 + r1 * cos(a0 + a1), y1 + r1 * sin(a0 + a1))); out[1] = make_pair(P(x0 + r0 * cos(a0 - a1), y0 + r0 * sin(a0 - a1)), P(x1 + r1 * cos(a0 - a1), y1 + r1 * sin(a0 - a1))); const double a2 = acos((r0 + r1) / abs(v)); out[2] = make_pair(P(x0 + r0 * cos(a0 + a2), y0 + r0 * sin(a0 + a2)), P(x1 + r1 * cos(a0 + a2 + PI), y1 + r1 * sin(a0 + a2 + PI))); out[3] = make_pair(P(x0 + r0 * cos(a0 - a2), y0 + r0 * sin(a0 - a2)), P(x1 + r1 * cos(a0 - a2 + PI), y1 + r1 * sin(a0 - a2 + PI))); } struct C { P p; double r; C(const P &p, double r) : p(p), r(r) { } bool contains(const C& c) const { return abs(p - c.p) + c.r < r; } bool contains(const P& p) const { return abs(p - this->p) < r; } }; struct Node { int nodeIndex; int circleIndex; P position; // (dstノード番号, 線種別) vector > edges; Node(int nodeIndex, int circleIndex, P position) : nodeIndex(nodeIndex), circleIndex(circleIndex), position(position), relativeAngle(0.0) { } double relativeAngle; bool operator < (const Node& rh) const { return relativeAngle < rh.relativeAngle; } }; // Spaghetti Source - 平面幾何の基本要素 http://www.prefield.com/algorithm/geometry/geometry.html namespace std { bool operator < (const P& a, const P& b) { return real(a) != real(b) ? real(a) < real(b) : imag(a) < imag(b); } } double cross(const P& a, const P& b) { return imag(conj(a)*b); } double dot(const P& a, const P& b) { return real(conj(a)*b); } struct L : public vector

{ L(const P &a, const P &b) { push_back(a); push_back(b); } }; // Spaghetti Source (ccw) - 点の進行方向 http://www.prefield.com/algorithm/geometry/ccw.html int ccw(P a, P b, P c) { b -= a; c -= a; if (cross(b, c) > 0) return +1; // counter clockwise if (cross(b, c) < 0) return -1; // clockwise if (dot(b, c) < 0) return +2; // c--a--b on line if (norm(b) < norm(c)) return -2; // a--b--c on line return 0; } // Spaghetti Source - 交差判定など http://www.prefield.com/algorithm/geometry/intersection.html bool intersectLL(const L &l, const L &m) { return abs(cross(l[1]-l[0], m[1]-m[0])) > EPS || // non-parallel abs(cross(l[1]-l[0], m[0]-l[0])) < EPS; // same line } bool intersectLS(const L &l, const L &s) { return cross(l[1]-l[0], s[0]-l[0])* // s[0] is left of l cross(l[1]-l[0], s[1]-l[0]) < EPS; // s[1] is right of l } bool intersectLP(const L &l, const P &p) { return abs(cross(l[1]-p, l[0]-p)) < EPS; } bool intersectSS(const L &s, const L &t) { return ccw(s[0],s[1],t[0])*ccw(s[0],s[1],t[1]) <= 0 && ccw(t[0],t[1],s[0])*ccw(t[0],t[1],s[1]) <= 0; } bool intersectSP(const L &s, const P &p) { return abs(s[0]-p)+abs(s[1]-p)-abs(s[1]-s[0]) < EPS; // triangle inequality } // Spaghetti Source - 距離など http://www.prefield.com/algorithm/geometry/distance.html P projection(const L &l, const P &p) { double t = dot(p-l[0], l[0]-l[1]) / norm(l[0]-l[1]); return l[0] + t*(l[0]-l[1]); } P reflection(const L &l, const P &p) { return p + 2.0 * (projection(l, p) - p); } double distanceLP(const L &l, const P &p) { return abs(p - projection(l, p)); } double distanceLL(const L &l, const L &m) { return intersectLL(l, m) ? 0 : distanceLP(l, m[0]); } double distanceLS(const L &l, const L &s) { if (intersectLS(l, s)) return 0; return min(distanceLP(l, s[0]), distanceLP(l, s[1])); } double distanceSP(const L &s, const P &p) { const P r = projection(s, p); if (intersectSP(s, r)) return abs(r - p); return min(abs(s[0] - p), abs(s[1] - p)); } double distanceSS(const L &s, const L &t) { if (intersectSS(s, t)) return 0; return min(min(distanceSP(s, t[0]), distanceSP(s, t[1])), min(distanceSP(t, s[0]), distanceSP(t, s[1]))); } P crosspoint(const L &l, const L &m) { double A = cross(l[1] - l[0], m[1] - m[0]); double B = cross(l[1] - l[0], l[1] - m[0]); if (abs(A) < EPS && abs(B) < EPS) return m[0]; // same line if (abs(A) < EPS) assert(false); // !!!PRECONDITION NOT SATISFIED!!! return m[0] + B / A * (m[1] - m[0]); } // false if l touches c. bool intersectLC(const L &l, const C& c) { return distanceLP(l, c.p) + EPS < c.r; } void crosspoint(const L& l, const C& c, P& p0, P& p1) { assert(intersectLC(l, c)); const P h = projection(l, c.p); const double d = abs(c.p - h); const P x = (l[0] - l[1]) / abs((l[0] - l[1])) * sqrt(c.r * c.r - d * d); p0 = h + x; p1 = h - x; } bool onSegment(const L& l, const P& p) { assert(abs(cross(l[1] - l[0], p - l[0])) < EPS); return dot(l[1] - l[0], p - l[0]) > EPS && dot(l[0] - l[1], p - l[1]) > EPS; } bool pickUp(const P& src, const P& dst, const vector& circles) { const int N = circles.size(); const L segment(src, dst); REP(i, N) { const C& c = circles[i]; if (!intersectLC(segment, c)) { continue; } P ps[2]; crosspoint(segment, c, ps[0], ps[1]); REP(j, 2) { const P& p = ps[j]; if (!onSegment(segment, p)) { continue; } // TODO(nodchip): Check the following condition. if (dot(p - c.p, dst - src) < -EPS) { return true; } } } return false; } //struct NodeComparator { // const P& center; // NodeComparator(const P& center) : center(center) { } // bool operator () (const Node& lh, const Node& rh) const { // return arg(lh.position - center) < arg(rh.position - center); // } //}; static const double kFrameLeft = 5.0; static const double kFrameBottom = 5.0; static const double kFrameSize = 580.0; static const char* kColors[] = {".0 white", "red", "green", "blue", "red + green", "green + blue", "blue + red", ".5 white"}; static const bool kEnabledColors[] = {true, true, true, true, true, true, true, }; class MetaPost { public: MetaPost() : left(1e20), right(-1e20), top(-1e20), bottom(1e20) { } void drawSegment(const L& l, int lineType) { REP(i, 2) { left = min(left, l[i].real()); right = max(right, l[i].real()); top = max(top, l[i].imag()); bottom = min(bottom, l[i].imag()); } segments.push_back(MP(l, lineType)); } void drawArrow(const L& l, int lineType) { REP(i, 2) { left = min(left, l[i].real()); right = max(right, l[i].real()); top = max(top, l[i].imag()); bottom = min(bottom, l[i].imag()); } arrows.push_back(MP(l, lineType)); } void drawCircle(const C& c, int lineType) { left = min(left, c.p.real() - c.r); right = max(right, c.p.real() + c.r); top = max(top, c.p.imag() + c.r); bottom = min(bottom, c.p.imag() - c.r); circles.push_back(MP(c, lineType)); } //void drawLetters(const P& p, const string& s) { // letters.push_back(MP(p, s)); //} void write(const char* filePath, const char* figureIndex) { const double d = max(right - left, top - bottom); right = left + d; top = bottom + d; FILE* file = fopen(filePath, "a"); fprintf(file, "beginfig(%s)\n", figureIndex); fprintf(file, "\tdefaultfont := \"rptmr\";\n"); { const L segment(P(-1.0, 0.0), P(1.0, 0.0)); const L normalized = scale(segment); fprintf(file, "\tdraw (%f,%f)--(%f,%f) withcolor .5white;\n", normalized[0].real(), normalized[0].imag(), normalized[1].real(), normalized[1].imag()); } { const L segment(P(0.0, -1.0), P(0.0, 1.0)); const L normalized = scale(segment); fprintf(file, "\tdraw (%f,%f)--(%f,%f) withcolor .5white;\n", normalized[0].real(), normalized[0].imag(), normalized[1].real(), normalized[1].imag()); } REP(i, segments.size()) { const L& segment = segments[i].first; const L normalized = scale(segment); const int lineType = segments[i].second; if (!kEnabledColors[lineType]) { continue; } fprintf(file, "\tdraw (%f,%f)--(%f,%f) withcolor %s;\n", normalized[0].real(), normalized[0].imag(), normalized[1].real(), normalized[1].imag(), kColors[lineType]); } REP(i, arrows.size()) { const L& segment = arrows[i].first; const L normalized = scale(segment); const int lineType = arrows[i].second; if (!kEnabledColors[lineType]) { continue; } fprintf(file, "\tdrawarrow (%f,%f)--(%f,%f) withcolor %s;\n", normalized[0].real(), normalized[0].imag(), normalized[1].real(), normalized[1].imag(), kColors[lineType]); fprintf(file, "\tdraw (%f,%f) withcolor %s withpen pencircle scaled 2bp;\n", normalized[0].real(), normalized[0].imag(), kColors[lineType]); } REP(i, circles.size()) { const C& circle = circles[i].first; const C normalized = scale(circle); const int lineType = circles[i].second; if (!kEnabledColors[lineType]) { continue; } fprintf(file, "\tdraw fullcircle scaled %f shifted (%f,%f) withcolor %s;\n", normalized.r * 2, normalized.p.real(), normalized.p.imag(), kColors[lineType]); } //REP(i, letters.size()) { // P p = letters[i].first; // p = scale(p); // fprintf(file, "\tlabel(btex $%s$ etex, (%f, %f));\n", letters[i].second.c_str(), p.real(), p.imag()); //} fprintf(file, "endfig;\n"); fclose(file); file = NULL; } private: double scale(double value, double minValue, double maxValue, double minFrame, double maxFrame) const { const double r = (value - minValue) / (maxValue - minValue); return r * (maxFrame - minFrame) + minFrame; } P scale(const P& p) const { return P(scale(p.real(), left, right, kFrameLeft, kFrameLeft + kFrameSize), scale(p.imag(), bottom, top, kFrameBottom, kFrameBottom + kFrameSize)); } L scale(const L& l) const { return L(scale(l[0]), scale(l[1])); } C scale(const C& c) const { return C(scale(c.p), c.r / (right - left) * kFrameSize); } double left; double right; double top; double bottom; vector > segments; vector > arrows; vector > circles; //vector > letters; }; int getCircleENWSIndex(int circleIndex, int direction) { return 1 + 4 * circleIndex + direction; } enum { TangentToTangent, ENWSToTangent, TangentToENWS, ENWSToENWS, InitToTangent, InitToENWS }; void checkConsistency(const vector& circles, const vector& nodes) { REP(i, nodes.size()) { const Node& node = nodes[i]; const int circleIndex = node.circleIndex; if (circleIndex < 0) { continue; } const C& circle = circles[circleIndex]; const double r = abs(node.position - circle.p); assert(abs(r - circle.r) < EPS); } } int main(int argc, char* argv[]) { for (int N; cin >> N && N; ) { double cx, cy; cin >> cx >> cy; vector circles; REP(n, N) { double x, y, r; cin >> x >> y >> r; circles.push_back(C(P(x, y), r)); } vector nodes; // 初期位置を登録する const P startPoint(cx, cy); nodes.push_back(Node(nodes.size(), -1, startPoint)); // 円の東西南北の点を登録する REP(circleIndex, N) { const C& c = circles[circleIndex]; REP(i, 4) { nodes.push_back(Node(nodes.size(), circleIndex, c.p + polar(c.r, PI * 0.5 * i))); } } checkConsistency(circles, nodes); // 円同士の接線 O(N^3) for (int i = 0; i < N; ++i) { const C& a = circles[i]; for (int j = i + 1; j < N; ++j) { const C& b = circles[j]; if (a.contains(b) || b.contains(a)) { continue; } pair out[4]; tangency(a.p.real(), a.p.imag(), a.r, b.p.real(), b.p.imag(), b.r, out); REP(k, 4) { const int srcIndex = nodes.size(); const int dstIndex = nodes.size() + 1; nodes.push_back(Node(srcIndex, i, out[k].first)); nodes.push_back(Node(dstIndex, j, out[k].second)); if (!pickUp(out[k].first, out[k].second, circles)) { nodes[srcIndex].edges.push_back(MP(dstIndex, (int)TangentToTangent)); } if (!pickUp(out[k].second, out[k].first, circles)) { nodes[dstIndex].edges.push_back(MP(srcIndex, (int)TangentToTangent)); } } } } checkConsistency(circles, nodes); // 円の東西南北と他の円への接線 O(N^3) REP(srcCircleIndex, N) { const C& srcCircle = circles[srcCircleIndex]; REP(dstCircleIndex, N) { if (srcCircleIndex == dstCircleIndex) { continue; } const C& dstCircle = circles[dstCircleIndex]; // 円の東西南北から外側の円に行く最短経路もあるはず // TODO(nodchip): 以下のケースをテストケースに加える //if (!a.contains(b)) { // continue; //} REP(k, 4) { //Node& srcNode = nodes[]; const int enwsNodeIndex = getCircleENWSIndex(srcCircleIndex, k); if (dstCircle.contains(nodes[enwsNodeIndex].position)) { continue; } P tangentPoints[2]; tangency(nodes[enwsNodeIndex].position.real(), nodes[enwsNodeIndex].position.imag(), dstCircle.p.real(), dstCircle.p.imag(), dstCircle.r, tangentPoints); // TODO(nodchip): aと線分との接触判定をチェックする // ccw(p, q, q)なら0が返る? // 円の東西南北から他の円の接線へのエッジ REP(l, 2) { const P& tangentPoint = tangentPoints[l]; // 他の円に乗り上げる場合はスキップする if (pickUp(nodes[enwsNodeIndex].position, tangentPoint, circles)) { continue; } // aの外側に向かう辺は、より良い経路が存在するはずなので無視してい良い const P nodeToCenter = srcCircle.p - nodes[enwsNodeIndex].position; const P nodeToTangent = tangentPoint - nodes[enwsNodeIndex].position; if (dot(nodeToCenter, nodeToTangent) < EPS) { continue; } const int tangentNodeIndex = nodes.size(); nodes.push_back(Node(tangentNodeIndex, dstCircleIndex, tangentPoint)); nodes[enwsNodeIndex].edges.push_back(MP(tangentNodeIndex, (int)ENWSToTangent)); } // 他の円の接線から円の東西南北へのエッジ REP(l, 2) { const P& tangentPoint = tangentPoints[l]; // 他の円に乗り上げる場合はスキップする if (pickUp(tangentPoint, nodes[enwsNodeIndex].position, circles)) { continue; } // aの外側に向かう辺は、より良い経路が存在するはずなので無視してい良い const int tangentNodeIndex = nodes.size(); nodes.push_back(Node(tangentNodeIndex, dstCircleIndex, tangentPoint)); nodes[tangentNodeIndex].edges.push_back(MP(enwsNodeIndex, (int)TangentToENWS)); } } } } checkConsistency(circles, nodes); // 円の東西南北から他の円の東西南北へ REP(srcCircleIndex, N) { REP(srcCircleDir, 4) { const int srcNodeIndex = getCircleENWSIndex(srcCircleIndex, srcCircleDir); const P& srcNodePosition = nodes[srcNodeIndex].position; for (int dstCircleIndex = srcCircleIndex + 1; dstCircleIndex < N; ++dstCircleIndex) { REP(dstCircleDir, 4) { const int dstNodeIndex = getCircleENWSIndex(dstCircleIndex, dstCircleDir); const P& dstNodePosition = nodes[dstNodeIndex].position; if (!pickUp(srcNodePosition, dstNodePosition, circles)) { nodes[srcNodeIndex].edges.push_back(MP(dstNodeIndex, (int)ENWSToENWS)); } if (!pickUp(dstNodePosition, srcNodePosition, circles)) { nodes[dstNodeIndex].edges.push_back(MP(srcNodeIndex, (int)ENWSToENWS)); } } } } } checkConsistency(circles, nodes); // 初期位置から各円への接線 REP(i, N) { const C& a = circles[i]; if (a.contains(startPoint)) { continue; } P tangentPoints[2]; tangency(cx, cy, a.p.real(), a.p.imag(), a.r, tangentPoints); REP(j, 2) { const P& tangentPoint = tangentPoints[j]; if (pickUp(P(cx, cy), tangentPoint, circles)) { continue; } nodes.push_back(Node(nodes.size(), i, tangentPoint)); nodes[0].edges.push_back(MP(nodes.back().nodeIndex, (int)InitToTangent)); } } checkConsistency(circles, nodes); // 初期位置から各円の東西南北への接線 REP(i, N) { const C& a = circles[i]; REP(j, 4) { const P pa = a.p + polar(a.r, PI * 0.5 * j); if (pickUp(nodes[0].position, pa, circles)) { continue; } nodes[0].edges.push_back(MP(getCircleENWSIndex(i, j), (int)InitToENWS)); } } checkConsistency(circles, nodes); #ifdef _DEBUG REP(nodeIndex, nodes.size()) { const Node& node = nodes[nodeIndex]; assert(node.nodeIndex == nodeIndex); } #endif // グラフの生成 vector > > g(nodes.size()); // 接線上の経路 REP(i, nodes.size()) { const Node& node = nodes[i]; const int src = node.nodeIndex; REP(j, node.edges.size()) { const int dst = node.edges[j].first; g[src].push_back(MP(dst, abs(node.position - nodes[dst].position))); } } // 各円上の経路 vector > nodeBuckets(N); REP(i, nodes.size()) { Node& node = nodes[i]; if (node.circleIndex < 0) { continue; } // 円の中心との相対角を計算する const C& circle = circles[node.circleIndex]; node.relativeAngle = arg(node.position - circle.p); // 各ノードを円に結びつける if (node.circleIndex >= 0) { nodeBuckets[node.circleIndex].push_back(node); } } REP(circleIndex, circles.size()) { vector& nodeBucket = nodeBuckets[circleIndex]; const C& circle = circles[circleIndex]; //sort(ALL(nodeBucket), NodeComparator(circle.p)); sort(ALL(nodeBucket)); REP(nodeIndex, nodeBucket.size()) { const Node& nodeA = nodeBucket[nodeIndex]; const Node& nodeB = nodeBucket[(nodeIndex + 1) % nodeBucket.size()]; const double thetaA = arg(nodeA.position - circle.p); const double thetaB = arg(nodeB.position - circle.p); double theta = thetaB - thetaA; while (theta < 0.0) { theta += PI2; } if (theta > PI) { theta = PI2 - theta; } const double d = theta * circle.r; g[nodeA.nodeIndex].push_back(MP(nodeB.nodeIndex, d)); g[nodeB.nodeIndex].push_back(MP(nodeA.nodeIndex, d)); } } // ダイクストラ vector dp(nodes.size(), 1e20); dp[0] = 0.0; priority_queue, vector >, greater > > q; q.push(MP(0.0, 0)); while (!q.empty()) { const double currentCost = q.top().first; const int currentNode = q.top().second; q.pop(); if (dp[currentNode] != currentCost) { continue; } REP(i, g[currentNode].size()) { const int nextNode = g[currentNode][i].first; const double nextCost = currentCost + g[currentNode][i].second; if (dp[nextNode] > nextCost) { dp[nextNode] = nextCost; q.push(MP(nextCost, nextNode)); } } } // 一番内側の円を求める vector > g2(N); REP(i, N) { REP(j, N) { if (i == j) { continue; } if (!circles[i].contains(circles[j])) { continue; } g2[i].push_back(j); } } vector heights(N); bool updated = true; while (updated) { updated = false; REP(src, N) { REP(j, g2[src].size()) { const int dst = g2[src][j]; if (heights[src] == heights[dst]) { updated = true; heights[dst] = heights[src] + 1; } } } } const int highestHeight = *max_element(ALL(heights)); // 解の出力 double bestAnswer = 1e20; // TODO(nodchip): 主人公が初めから目標地点にいる場合のテストケース REP(circleIndex, circles.size()) { const C& circle = circles[circleIndex]; if (heights[circleIndex] != highestHeight) { continue; } if (circle.contains(P(cx, cy))) { bestAnswer = 0.0; } } //REP(nodeIndex, nodes.size()) { // printf("%3d: %15.10f(%15.10f, %15.10f)\n", nodeIndex, dp[nodeIndex], nodes[nodeIndex].position.real(), nodes[nodeIndex].position.imag()); //} REP(nodeIndex, nodes.size()) { const Node& node = nodes[nodeIndex]; if (node.circleIndex < 0) { continue; } if (heights[node.circleIndex] != highestHeight) { continue; } // If the position of the node is neither north, south, east nor west of a center of the circle where the node is on, we should ignore. // Patched by hmuta. if (abs(real(node.position - circles[node.circleIndex].p)) > EPS && abs(imag(node.position - circles[node.circleIndex].p)) > EPS) { continue; } bestAnswer = min(bestAnswer, dp[nodeIndex]); } printf("%.20f\n", bestAnswer); if (argc == 2) { MetaPost mp; REP(i, circles.size()) { const C& circle = circles[i]; mp.drawCircle(circle, 0); //char buffer[1024]; //sprintf(buffer, "%.0f %.0f", circle.p.real(), circle.p.imag()); //mp.drawLetters(circle.p, buffer); } REP(srcNodeIndex, nodes.size()) { const Node& srcNode = nodes[srcNodeIndex]; REP(i, srcNode.edges.size()) { const int dstNodeIndex = srcNode.edges[i].first; const Node& dstNode = nodes[dstNodeIndex]; const int lineType = srcNode.edges[i].second; mp.drawArrow(L(srcNode.position, dstNode.position), lineType + 1); } } mp.write("visualize.mp", argv[1]); } } }