import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.List;
import java.util.*;

/**
 * Image Scanner
 */

class FastPrint {
    public static final PrintWriter out = new PrintWriter(new BufferedOutputStream(System.out));
}

/**
 * Comparator
 */
class PromptValueThenKeyComparator<K extends Comparable<? super K>,
        V extends Comparable<? super V>>
        implements Comparator<Map.Entry<K, V>> {

    // Stable sort the Key as required in the prompt
    public int compare(Map.Entry<K, V> a, Map.Entry<K, V> b) {
        int cmp1 = b.getValue().compareTo(a.getValue());
        if (cmp1 != 0) {
            return cmp1;
        } else {
            String[] a_key = ((String) a.getKey()).split(" - "), b_key = ((String) b.getKey()).split(" - ");
            int delta = Integer.compare(Integer.parseInt(a_key[0]), Integer.parseInt(b_key[0]));
            if (delta == 0) return Integer.compare(Integer.parseInt(a_key[1]), Integer.parseInt(b_key[1]));
            return delta;
        }
    }

}

// N images, at most P pixels and H hull vertices per image:
// O(N*P*log(P) + N*N*H*H + M*log(M)), where M=N*(N-1)/2.
public class
Solution {
    /**
     * Turns the BMP into a list of vertices of the convex hull
     * Time complexity: O(42²+N*lg N) where N is the number of white pixels
     */
    public static List<Point> bmp_to_convex_shape(BufferedImage image) {
        List<Point> coordinates = new LinkedList<>();

        for (int row = 0; row < image.getWidth(); ++row) {
            for (int col = 0; col < image.getHeight(); ++col) {
                if (image.getRGB(row, col) == Color.white.getRGB()) {
                    coordinates.add(new Point(row, col)); // O(1) - we're using a Doubly Linked List
                }
            }
        }
        if (coordinates.size() < 3) throw new IllegalArgumentException("Not enough edge points");
        // O(n * lg n)
        return GrahamScan.getConvexHull(coordinates);
    }

    /**
     * This method will be used to calculate the Hausdorff distance
     * Time complexity: O(2*(A*B))
     *
     * @param simple_convex_A List<Point> Simple Convex Polygon A
     * @param simple_convex_B List<Point> Simple Convex Polygon B
     * @return double Returns the Hausdorff's distance
     * @author Diogo Peralta Cordeiro <diogo@fc.up.pt>
     */
    public static double
    hausdorff_distance(List<Point> simple_convex_A, List<Point> simple_convex_B) {
        // This is necessary as we want the maximum of the (supremums of the infimums)
        double max_dist_A_B = directed_hausdorff_distance(simple_convex_A, simple_convex_B),
                max_dist_B_A = directed_hausdorff_distance(simple_convex_B, simple_convex_A);
        return Math.max(max_dist_A_B, max_dist_B_A);
    }

    // Let A be the total of points in simple_convex_A, and B in simple_convex_B
    // Time complexity: O(B*A)
    public static double
    directed_hausdorff_distance(List<Point> simple_convex_A, List<Point> simple_convex_B) {
        double hausdorff_distance_A_B = -1;

        // For each vertex of B,
        for (Point b : simple_convex_B) {
            // we look for the minimum distance to the vertices of A.
            double min_dist = 1000000000;
            for (Point a : simple_convex_A) {
                double dx = (a.x - b.x),
                        dy = (a.y - b.y),
                        min_dist_candidate = dx * dx + dy * dy;

                if (min_dist_candidate < min_dist) {
                    min_dist = min_dist_candidate;
                }
                // Can't be smaller than 0 so, we can stop looking.
                if (min_dist_candidate == 0) {
                    break;
                }
            }
            // And then we pick the maximum of these.
            hausdorff_distance_A_B = Math.max(hausdorff_distance_A_B, min_dist);
        }
        // We finally square root so it is the distance and not the square of it.
        return Math.sqrt(hausdorff_distance_A_B);
    }

    // Build hulls, evaluate every pair, sort by score and citizen identifiers.
    public static void
    main(String[] args) {
        final int N_images = 1337;
        final int len_image = 398;

        // International Soul Identifier, Convex hull
        HashMap<Integer, List<Point>> graph_nodes = new HashMap<>(N_images);
        int n_nodes = 0;
        try {
            byte[] input = System.in.readNBytes(N_images * len_image + 1);
            if (input.length != N_images * len_image) throw new IllegalArgumentException("Wrong input length");
            for (int offset = 0; offset < input.length; offset += len_image) {
                java.nio.ByteBuffer header = java.nio.ByteBuffer.wrap(input, offset, len_image).slice().order(java.nio.ByteOrder.LITTLE_ENDIAN);
                if (header.get(0) != 'B' || header.get(1) != 'M' || header.getInt(2) != len_image ||
                    header.getInt(10) != 62 || header.getInt(14) != 40 || header.getInt(18) != 42 || header.getInt(22) != 42 ||
                    header.getShort(26) != 1 || header.getShort(28) != 1 || header.getInt(30) != 0)
                    throw new IllegalArgumentException("Expected an uncompressed 42 by 42 monochrome BMP");
                if ((header.getInt(54) & 0x00ffffff) != 0 || (header.getInt(58) & 0x00ffffff) != 0x00ffffff)
                    throw new IllegalArgumentException("Expected black then white palette entries");
                BufferedImage image = ImageIO.read(new ByteArrayInputStream(input, offset, len_image));
                if (image == null) throw new IllegalArgumentException("Unreadable image");
                graph_nodes.put(n_nodes++, bmp_to_convex_shape(image));
            }
        } catch (IOException | IllegalArgumentException exception) {
            System.out.print("And if it wasn't for you, baby,\nI really think that I would\nhave somebody else.\n");
            return;
        }
        //System.out.println(n_nodes);

        // We can create a complete undirected graph of N nodes (N_images) and
        // N*(N-1)/2 edges with the weight being the soul mate likelihood
        HashMap<String, Double> undirected_graph = new HashMap<>(N_images*(N_images-1)/2);
        for (int u = 0; u < n_nodes; ++u) {
            for (int w = 0; w < u; ++w) {
                // We want to stable sort
                String link = w + " - " + u;

                // Match percentage is inversely proportional to soul mate distance
                double distance = hausdorff_distance(graph_nodes.get(u), graph_nodes.get(w)); // O(Hu * Hw)
                // The problem defines a fixed normalisation of sqrt(42*42 + 42*42).
                double match_percentage = 100 - (distance / Math.sqrt(42 * 42 + 42 * 42)) * 100;

                undirected_graph.put(link, match_percentage); // O(1)
            }
        }

        // Output O(N*(N-1)/2), N = N_images
        List<Map.Entry<String, Double>> list = new ArrayList<>(undirected_graph.entrySet());
        list.sort(new PromptValueThenKeyComparator<>()); // O(N*lg(N))
        for (Map.Entry<String, Double> i : list) {
            FastPrint.out.printf(Locale.ROOT, "%s: %.2f%%\n", i.getKey(), i.getValue());
        }
        FastPrint.out.close();
    }

    /**
     * Auxiliary debug method that turns a List of points into a BMP file
     */
    private static void point_to_img_file(List<Point> shape, String s) {
        final int N = 42;
        BufferedImage image = new BufferedImage(N, N, BufferedImage.TYPE_BYTE_BINARY);
        for (Point point : shape) {
            image.setRGB(point.x, point.y, Color.white.getRGB());
        }
        try {
            ImageIO.write(image, "BMP", new File(s + ".bmp"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//----------------------------------------------------------------------

/**
 * An {@code InputStream} wrapper that provides up to a maximum number of
 * bytes from the underlying stream.  Does not support mark/reset, even
 * when the wrapped stream does, and does not perform any buffering.
 *
 * @link https://stackoverflow.com/a/28119691
 */
class BoundedInputStream extends InputStream {

    /**
     * This stream's underlying @{code InputStream}
     */
    private final InputStream data;

    /**
     * The maximum number of bytes still available from this stream
     */
    private long bytesRemaining;

    /**
     * Initializes a new {@code BoundedInputStream} with the specified
     * underlying stream and byte limit
     *
     * @param data     the @{code InputStream} serving as the source of this
     *                 one's data
     * @param maxBytes the maximum number of bytes this stream will deliver
     *                 before signaling end-of-data
     */
    public BoundedInputStream(InputStream data, long maxBytes) {
        this.data = data;
        bytesRemaining = Math.max(maxBytes, 0);
    }

    @Override
    public int available() throws IOException {
        return (int) Math.min(data.available(), bytesRemaining);
    }

    @Override
    public void close() throws IOException {
        data.close();
    }

    @Override
    public synchronized void mark(int limit) {
        // does nothing
    }

    @Override
    public boolean markSupported() {
        return false;
    }

    @Override
    public int read(byte[] buf, int off, int len) throws IOException {
        if (bytesRemaining > 0) {
            int nRead = data.read(
                    buf, off, (int) Math.min(len, bytesRemaining));

            bytesRemaining -= nRead;

            return nRead;
        } else {
            return -1;
        }
    }

    @Override
    public int read(byte[] buf) throws IOException {
        return this.read(buf, 0, buf.length);
    }

    @Override
    public synchronized void reset() throws IOException {
        throw new IOException("reset() not supported");
    }

    @Override
    public long skip(long n) throws IOException {
        long skipped = data.skip(Math.min(n, bytesRemaining));

        bytesRemaining -= skipped;

        return skipped;
    }

    @Override
    public int read() throws IOException {
        if (bytesRemaining > 0) {
            int c = data.read();

            if (c >= 0) {
                bytesRemaining -= 1;
            }

            return c;
        } else {
            return -1;
        }
    }
}

// from: https://github.com/bkiers/GrahamScan/blob/master/src/main/cg/GrahamScan.java
final class GrahamScan {

    /**
     * Returns true iff all points in <code>points</code> are collinear.
     *
     * @param points the list of points.
     * @return true iff all points in <code>points</code> are collinear.
     */
    protected static boolean areAllCollinear(List<Point> points) {

        if (points.size() < 2) {
            return true;
        }

        final Point a = points.get(0);
        final Point b = points.get(1);

        for (int i = 2; i < points.size(); ++i) {

            Point c = points.get(i);

            if (getTurn(a, b, c) != Turn.COLLINEAR) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns the convex hull of the points created from <code>xs</code>
     * and <code>ys</code>. Note that the first and last point in the returned
     * <code>List&lt;java.awt.Point&gt;</code> are the same point.
     *
     * @param xs the x coordinates.
     * @param ys the y coordinates.
     * @return the convex hull of the points created from <code>xs</code>
     * and <code>ys</code>.
     * @throws IllegalArgumentException if <code>xs</code> and <code>ys</code>
     *                                  don't have the same size, if all points
     *                                  are collinear or if there are less than
     *                                  3 unique points present.
     */
    public static List<Point> getConvexHull(int[] xs, int[] ys) throws IllegalArgumentException {

        if (xs.length != ys.length) {
            throw new IllegalArgumentException("xs and ys don't have the same size");
        }

        List<Point> points = new ArrayList<>();

        for (int i = 0; i < xs.length; i++) {
            points.add(new Point(xs[i], ys[i]));
        }

        return getConvexHull(points);
    }

    /**
     * Returns the convex hull of the points created from the list
     * <code>points</code>. Note that the first and last point in the
     * returned <code>List&lt;java.awt.Point&gt;</code> are the same
     * point.
     *
     * @param points the list of points.
     * @return the convex hull of the points created from the list
     * <code>points</code>.
     * @throws IllegalArgumentException if all points are collinear or if there
     *                                  are less than 3 unique points present.
     */
    public static List<Point> getConvexHull(List<Point> points) throws IllegalArgumentException {

        List<Point> sorted = new ArrayList<>(getSortedPointSet(points));

        if (sorted.size() < 3) {
            throw new IllegalArgumentException("can only create a convex hull of 3 or more unique points");
        }

        if (areAllCollinear(sorted)) {
            throw new IllegalArgumentException("cannot create a convex hull from collinear points");
        }

        Stack<Point> stack = new Stack<>();
        stack.push(sorted.get(0));
        stack.push(sorted.get(1));

        for (int i = 2; i < sorted.size(); ++i) {

            Point head = sorted.get(i);
            Point middle = stack.pop();
            Point tail = stack.peek();

            Turn turn = getTurn(tail, middle, head);

            switch (turn) {
                case COUNTER_CLOCKWISE:
                    stack.push(middle);
                    stack.push(head);
                    break;
                case CLOCKWISE:
                    --i;
                    break;
                case COLLINEAR:
                    stack.push(head);
                    break;
            }
        }

        // close the hull
        stack.push(sorted.get(0));

        return new ArrayList<>(stack);
    }

    /**
     * Returns the points with the lowest y coordinate. In case more than 1 such
     * point exists, the one with the lowest x coordinate is returned.
     *
     * @param points the list of points to return the lowest point from.
     * @return the points with the lowest y coordinate. In case more than
     * 1 such point exists, the one with the lowest x coordinate
     * is returned.
     */
    protected static Point getLowestPoint(List<Point> points) {

        Point lowest = points.get(0);

        for (int i = 1; i < points.size(); i++) {

            Point temp = points.get(i);

            if (temp.y < lowest.y || (temp.y == lowest.y && temp.x < lowest.x)) {
                lowest = temp;
            }
        }

        return lowest;
    }

    /**
     * Returns a sorted set of points from the list <code>points</code>. The
     * set of points are sorted in increasing order of the angle they and the
     * lowest point <tt>P</tt> make with the x-axis. If tow (or more) points
     * form the same angle towards <tt>P</tt>, the one closest to <tt>P</tt>
     * comes first.
     *
     * @param points the list of points to sort.
     * @return a sorted set of points from the list <code>points</code>.
     * @see GrahamScan#getLowestPoint(java.util.List)
     */
    protected static Set<Point> getSortedPointSet(List<Point> points) {

        final Point lowest = getLowestPoint(points);

        TreeSet<Point> set = new TreeSet<>((a, b) -> {

            if (a == b || a.equals(b)) {
                return 0;
            }

            // use longs to guard against int-underflow
            double thetaA = Math.atan2((long) a.y - lowest.y, (long) a.x - lowest.x);
            double thetaB = Math.atan2((long) b.y - lowest.y, (long) b.x - lowest.x);

            if (thetaA < thetaB) {
                return -1;
            } else if (thetaA > thetaB) {
                return 1;
            } else {
                // collinear with the 'lowest' point, let the point closest to it come first

                // use longs to guard against int-over/underflow
                double distanceA = Math.sqrt((((long) lowest.x - a.x) * ((long) lowest.x - a.x)) +
                        (((long) lowest.y - a.y) * ((long) lowest.y - a.y)));
                double distanceB = Math.sqrt((((long) lowest.x - b.x) * ((long) lowest.x - b.x)) +
                        (((long) lowest.y - b.y) * ((long) lowest.y - b.y)));

                if (distanceA < distanceB) {
                    return -1;
                } else {
                    return 1;
                }
            }
        });

        set.addAll(points);

        return set;
    }

    /**
     * Returns the GrahamScan#Turn formed by traversing through the
     * ordered points <code>a</code>, <code>b</code> and <code>c</code>.
     * More specifically, the cross product <tt>C</tt> between the
     * 3 points (vectors) is calculated:
     *
     * <tt>(b.x-a.x * c.y-a.y) - (b.y-a.y * c.x-a.x)</tt>
     * <p>
     * and if <tt>C</tt> is less than 0, the turn is CLOCKWISE, if
     * <tt>C</tt> is more than 0, the turn is COUNTER_CLOCKWISE, else
     * the three points are COLLINEAR.
     *
     * @param a the starting point.
     * @param b the second point.
     * @param c the end point.
     * @return the GrahamScan#Turn formed by traversing through the
     * ordered points <code>a</code>, <code>b</code> and
     * <code>c</code>.
     */
    protected static Turn getTurn(Point a, Point b, Point c) {

        // use longs to guard against int-over/underflow
        long crossProduct = (((long) b.x - a.x) * ((long) c.y - a.y)) -
                (((long) b.y - a.y) * ((long) c.x - a.x));

        if (crossProduct > 0) {
            return Turn.COUNTER_CLOCKWISE;
        } else if (crossProduct < 0) {
            return Turn.CLOCKWISE;
        } else {
            return Turn.COLLINEAR;
        }
    }

    /**
     * An enum denoting a directional-turn between 3 points (vectors).
     */
    protected enum Turn {CLOCKWISE, COUNTER_CLOCKWISE, COLLINEAR}
}
