import java.util.*;
import java.util.concurrent.*;

public class Euler262 {
    static class Point {
        double x, y;

        Point(double x, double y) {
            this.x = x;
            this.y = y;
        }
    }

    static double height(double x, double y) {
        double xx = x * x;
        double yy = y * y;
        double xy = x * y;
        double base = 5000.0 - 0.005 * (xx + yy + xy) + 12.5 * (x + y);
        double expo = -Math.abs(0.000001 * (xx + yy) - 0.0015 * (x + y) + 0.7);
        return base * Math.exp(expo);
    }

    static double dist(Point a, Point b) {
        return Math.hypot(a.x - b.x, a.y - b.y);
    }

    static class Key {
        long x, y;

        Key(long x, long y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (!(o instanceof Key))
                return false;
            Key other = (Key) o;
            return x == other.x && y == other.y;
        }

        @Override
        public int hashCode() {
            long hashX = x;
            return (int) ((hashX * 0x9e3779b97f4a7c15L) ^ (y + (hashX << 6) + (hashX >> 2)));
        }
    }

    static Key quantize(Point p, double scale) {
        return new Key(Math.round(p.x * scale), Math.round(p.y * scale));
    }

    static Point fromKey(Key k, double scale) {
        return new Point(k.x / scale, k.y / scale);
    }

    static Point interp(Point p1, Point p2, double v1, double v2, double f) {
        double t;
        double denom = v2 - v1;
        if (Math.abs(denom) < 1e-14) {
            t = 0.5;
        } else {
            t = (f - v1) / denom;
            if (t < 0.0)
                t = 0.0;
            if (t > 1.0)
                t = 1.0;
        }
        return new Point(p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
    }

    static class Node implements Comparable<Node> {
        double cost;
        int idx;

        Node(double cost, int idx) {
            this.cost = cost;
            this.idx = idx;
        }

        public int compareTo(Node o) {
            return Double.compare(this.cost, o.cost);
        }
    }

    public static String solve() {
        int kMaxCoord = 1600;
        double gridStep = 1.0;
        double visStep = 0.5;
        int threads = Math.max(1, Runtime.getRuntime().availableProcessors());

        int n = (int) (kMaxCoord / gridStep) + 1;
        double[] heights = new double[n * n];

        int useThreads = Math.min(threads, n);
        int chunk = (n + useThreads - 1) / useThreads;
        ExecutorService executor = Executors.newFixedThreadPool(useThreads);
        List<Future<?>> futures = new ArrayList<>();

        for (int t = 0; t < useThreads; ++t) {
            final int start = t * chunk;
            final int end = Math.min(n, start + chunk);
            if (start >= end)
                break;
            futures.add(executor.submit(() -> {
                for (int i = start; i < end; ++i) {
                    double x = i * gridStep;
                    int base = i * n;
                    for (int j = 0; j < n; ++j) {
                        double y = j * gridStep;
                        heights[base + j] = height(x, y);
                    }
                }
            }));
        }

        for (Future<?> f : futures) {
            try {
                f.get();
            } catch (Exception e) {
            }
        }
        executor.shutdown();

        Point A = new Point(200.0, 200.0);
        Point B = new Point(1400.0, 1400.0);
        int startIdx = (int) (A.x / gridStep) * n + (int) (A.y / gridStep);
        int goalIdx = (int) (B.x / gridStep) * n + (int) (B.y / gridStep);

        double[] bestHeights = new double[n * n];
        Arrays.fill(bestHeights, Double.POSITIVE_INFINITY);

        PriorityQueue<Node> pq = new PriorityQueue<>();
        bestHeights[startIdx] = heights[startIdx];
        pq.offer(new Node(bestHeights[startIdx], startIdx));

        int[][] dirs = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };
        double fMin = Double.POSITIVE_INFINITY;

        while (!pq.isEmpty()) {
            Node curr = pq.poll();
            if (curr.cost != bestHeights[curr.idx])
                continue;
            if (curr.idx == goalIdx) {
                fMin = curr.cost;
                break;
            }

            int i = curr.idx / n;
            int j = curr.idx % n;
            for (int[] d : dirs) {
                int ni = i + d[0];
                int nj = j + d[1];
                if (ni < 0 || ni >= n || nj < 0 || nj >= n)
                    continue;
                int nidx = ni * n + nj;
                double next = Math.max(curr.cost, heights[nidx]);
                if (next < bestHeights[nidx]) {
                    bestHeights[nidx] = next;
                    pq.offer(new Node(next, nidx));
                }
            }
        }

        byte[] inside = new byte[n * n];
        for (int idx = 0; idx < heights.length; ++idx) {
            if (heights[idx] > fMin)
                inside[idx] = 1;
        }

        int[] kCaseCount = { 0, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0 };
        int[][][] kCaseEdges = {
                { { -1, -1 }, { -1, -1 } },
                { { 3, 0 }, { -1, -1 } },
                { { 0, 1 }, { -1, -1 } },
                { { 3, 1 }, { -1, -1 } },
                { { 1, 2 }, { -1, -1 } },
                { { 3, 2 }, { 0, 1 } },
                { { 0, 2 }, { -1, -1 } },
                { { 3, 2 }, { -1, -1 } },
                { { 2, 3 }, { -1, -1 } },
                { { 0, 2 }, { -1, -1 } },
                { { 0, 1 }, { 2, 3 } },
                { { 1, 2 }, { -1, -1 } },
                { { 3, 1 }, { -1, -1 } },
                { { 0, 1 }, { -1, -1 } },
                { { 3, 0 }, { -1, -1 } },
                { { -1, -1 }, { -1, -1 } }
        };

        List<Point[]> segments = new ArrayList<>();
        for (int i = 0; i < n - 1; ++i) {
            double x = i * gridStep;
            for (int j = 0; j < n - 1; ++j) {
                double y = j * gridStep;
                int idxLL = i * n + j;
                int idxLR = (i + 1) * n + j;
                int idxUR = (i + 1) * n + (j + 1);
                int idxUL = i * n + (j + 1);

                int mask = (inside[idxLL] == 1 ? 1 : 0) |
                        (inside[idxLR] == 1 ? 2 : 0) |
                        (inside[idxUR] == 1 ? 4 : 0) |
                        (inside[idxUL] == 1 ? 8 : 0);
                if (mask == 0 || mask == 15)
                    continue;

                Point ll = new Point(x, y);
                Point lr = new Point(x + gridStep, y);
                Point ur = new Point(x + gridStep, y + gridStep);
                Point ul = new Point(x, y + gridStep);

                Point[] edges = {
                        interp(ll, lr, heights[idxLL], heights[idxLR], fMin),
                        interp(lr, ur, heights[idxLR], heights[idxUR], fMin),
                        interp(ul, ur, heights[idxUL], heights[idxUR], fMin),
                        interp(ll, ul, heights[idxLL], heights[idxUL], fMin)
                };

                for (int s = 0; s < kCaseCount[mask]; ++s) {
                    int e0 = kCaseEdges[mask][s][0];
                    int e1 = kCaseEdges[mask][s][1];
                    if (e0 < 0 || e1 < 0)
                        continue;
                    segments.add(new Point[] { edges[e0], edges[e1] });
                }
            }
        }

        double keyScale = 1e6;
        Map<Key, List<Key>> adj = new HashMap<>();
        for (Point[] seg : segments) {
            Key k1 = quantize(seg[0], keyScale);
            Key k2 = quantize(seg[1], keyScale);
            adj.computeIfAbsent(k1, k -> new ArrayList<>()).add(k2);
            adj.computeIfAbsent(k2, k -> new ArrayList<>()).add(k1);
        }

        Set<Key> visited = new HashSet<>();
        List<Key> contourKeys = new ArrayList<>();
        double bestPerimeter = -1.0;
        Key prevDummy = new Key(Long.MIN_VALUE, Long.MIN_VALUE);

        for (Key startKey : adj.keySet()) {
            if (visited.contains(startKey))
                continue;

            List<Key> loop = new ArrayList<>();
            Key prev = prevDummy;
            Key cur = startKey;

            while (true) {
                if (visited.contains(cur))
                    break;
                visited.add(cur);
                loop.add(cur);
                List<Key> nbrs = adj.get(cur);
                Key nxt = nbrs.get(0);
                if (prev.equals(nxt) && nbrs.size() > 1) {
                    nxt = nbrs.get(1);
                }
                prev = cur;
                cur = nxt;
            }

            double per = 0.0;
            for (int i = 0; i < loop.size(); ++i) {
                Point a = fromKey(loop.get(i), keyScale);
                Point b = fromKey(loop.get((i + 1) % loop.size()), keyScale);
                per += dist(a, b);
            }

            if (per > bestPerimeter) {
                bestPerimeter = per;
                contourKeys = loop;
            }
        }

        List<Point> contour = new ArrayList<>();
        for (Key k : contourKeys) {
            contour.add(fromKey(k, keyScale));
        }

        int m = contour.size();
        double[] prefix = new double[m + 1];
        for (int i = 0; i < m; ++i) {
            prefix[i + 1] = prefix[i] + dist(contour.get(i), contour.get((i + 1) % m));
        }
        double perimeter = prefix[m];

        List<Integer> visA = new ArrayList<>();
        List<Integer> visB = new ArrayList<>();
        double[] distA = new double[m];
        double[] distB = new double[m];

        for (int i = 0; i < m; ++i) {
            distA[i] = dist(A, contour.get(i));
            distB[i] = dist(B, contour.get(i));
            if (visible(A, contour.get(i), fMin, visStep))
                visA.add(i);
            if (visible(B, contour.get(i), fMin, visStep))
                visB.add(i);
        }

        double bestDist = Double.POSITIVE_INFINITY;
        if (visible(A, B, fMin, visStep)) {
            bestDist = dist(A, B);
        }

        for (int i : visA) {
            for (int j : visB) {
                double arc = Math.abs(prefix[j] - prefix[i]);
                arc = Math.min(arc, perimeter - arc);
                double total = distA[i] + distB[j] + arc;
                if (total < bestDist) {
                    bestDist = total;
                }
            }
        }

        return String.format(Locale.US, "%.3f", bestDist);
    }

    static boolean visible(Point a, Point b, double f, double step) {
        double dx = b.x - a.x;
        double dy = b.y - a.y;
        double distAb = Math.hypot(dx, dy);
        if (distAb == 0.0)
            return true;
        int samples = Math.max(1, (int) (distAb / step));
        for (int s = 1; s < samples; ++s) {
            double t = (double) s / samples;
            if (height(a.x + dx * t, a.y + dy * t) > f + 1e-10)
                return false;
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(solve());
    }
}
