import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Scanner;


/**
 * 
 */

/**
 * 
 * Sample solution for Tsunami
 * 
 * @author zeil
 *
 */
public class Tsunami_zeil {
	
	static int testNum;
	
	public class Point extends java.awt.Point {
		public Point (int x, int y) {
			super(x,y);
		}
		
		public String toString() {
			return "(" + x + "," + y + ")";
		}
	}

	
	public class City extends Point {
		public Graph belongsTo;
		
		public City (int x, int y) {
			super(x,y);
			belongsTo = null;
		}
	}
	
	
	public class Edge implements Comparable<Edge> {
		public City from;
		public City to;
		public int distance;
		
		public Edge (City f, City t) {
			from = f;
			to = t;
			int dx = f.x - t.x;
			int dy = f.y - t.y;
			distance = dx*dx + dy*dy;
		}
		
		@Override
		public int compareTo(Edge o) {
			if (to.y != o.to.y)
				return to.y - o.to.y;
			else
				return distance - o.distance;
		}
		
		public String toString() {
			return "[" + from + "," + to + "]";
		}
	}

	class Graph {
		City identifier;
		HashSet<City> vertices;
		HashSet<Edge> edges;
		
		public Graph (City id) {
			identifier = id;
			vertices = new HashSet<City>();
			vertices.add(id);
			id.belongsTo = this;
			edges = new HashSet<Edge>();
		}
		
		/**
		 * Combines edges of g into this graph and marks the
		 * cities of G as belonging to this graph (essentially
		 * the union part of the classic union-find algorithm).
		 * 
		 * @param g graph that is giving up its cities and edges to this one
		 */
		public void merge (Graph g) {
			edges.addAll(g.edges);
			for (City c : g.vertices) {
				vertices.add(c);
				c.belongsTo = this;
			}
		}
		
	}
	
	ArrayList<Edge> allEdges;
	City[] cities;
	
	
	static boolean DEBUG = false;
	
	
	public Tsunami_zeil(Scanner in, int n) {
		allEdges = new ArrayList<Tsunami_zeil.Edge>();
		cities = new City[n];
		// Initially, each city belongs to a graph containing only
		// that city as its vertices and no edges to start with/
		for (int i = 0; i < n; ++i) {
			int x = in.nextInt();
			int y = in.nextInt();
			cities[i] = new City(x, y);
			cities[i].belongsTo = new Graph(cities[i]);
		}
	}

	final static void run (BufferedReader bin)
	{
		testNum = 0;
		Scanner in = new Scanner(bin);
		int n = in.nextInt();
		while (n > 0)
		{
			processDataSet(in, n);
			n = in.nextInt();
			++testNum;
		}
	}

	private static void processDataSet(Scanner in, int n) {
		Tsunami_zeil tsunami = new Tsunami_zeil(in, n);
		tsunami.solve();
	}


	private void solve() {
		// Generate edges between cities. No edges from cities further away
		// from the shore to cities closer to the shore.
		for (int i = 0; i < cities.length; ++i) {
			City c1 = cities[i];
			for (int j = i+1; j < cities.length; ++j) {
				City c2 = cities[j];
				if (c1.y >= c2.y) {
					allEdges.add(new Edge(c2, c1));
				}
				if (c2.y >= c1.y) {
					allEdges.add(new Edge(c1, c2));
				}
			}
		}
		// Find the minimum spanning tree
		mst();
		// Add up the lengths of all edges in the MST
		double sum = 0.0;
		Graph mst = cities[0].belongsTo; // All cities should now belong to the same graph
		for (Edge e: mst.edges) {
			double dist = Math.sqrt((double)e.distance);
			sum += dist;
		}
		System.out.format ("%.2f%n", sum);
	    long sanityCheck = (long)(10000.0 * sum);
	    if (sanityCheck % 100 == 50) {
	    	System.err.println ("Test case " + testNum + " is prone to round-off error");
	    }
	}

	private void mst() {
		// A modified version of Krukal's algorithm for finding minimum spanning trees
		// in undirected graphs.
		//
		// Normally, finding MST's in directed graphs would be a pretty challenging
		// algorithm. But the geometry of this problem lends itself to a simpler
		// approach.
		//
		// Theorem: Given a graph G of vertices with (x,y) coordinates and edges in which
		//    the destination vertex y value is always greater or equal to the source vertex
		//    y value, let T be an MST of that graph with minimal sum of edge distances.
		//    Let Below(H,y) denote the graph formed be removing from graph H all all 
		//    edges of H with a destination above y and all vertices of H above y.
		//    Then for any y0 such that one or more vertices of G appear both above and
		//    below y0, Below(T,y0) is an MST with minimal sum of edges of the 
		//    graph Below(G,y0).
		//  Proof: If this were not true, either Below(T,y0) would have to not be a tree or
		//    or would have to not be minimal. Below(T,y0) is a subgraph of the tree T, so it
		//    must be a tree unless it is disconnected. If it is disconnected, there must be
		//    a path in T linking the disconnected components. That path would have to include
		//    at least one edge from a city above y0 to a city below y0, and no such edge 
		//    can exist. So Below(T,y0) must be connected and must be a tree. 
		//    Assume, then, that Below(T,y0) is not minimal, that there is another tree
		//    T' in Below(G,y0) with smaller sum of edge distances.  Let G' be
		//    the graph formed from the edges og G not in Below(G,y0), so that 
		//    G = union(Below(G,y0), G'). Then union(T',G') is also a spanning tree of G,
		//    and has a smaller sum of edge distances than T, contradicting the assumption
		//    that T was an MST of G.
		//    
		//  So we can envision an approach to finding the MST of G by using cutting plane
		//  y = y0 and slowly increasing y0. If y0 rises just enough to add one city C to
		//  the graph, we connect it to the cities below by the shotest available edge to C.
		//  If y0 increases to a point where it adds multiple cities with matching Y 
		//  values, then we repeatedly add the shortest edges that connect cities not yet
		//  joined to the MST constructed so far.
		//
		//  This can be done by a simple modification of Kruskals algorithm in which
		//  instead of sorting the edges by distance, we sort them in order of increasing
		//  y values of the destination, breaking ties in favor of the shorter edges.
		//
		Collections.sort(allEdges);
		for (Edge e: allEdges) {
			if (e.from.belongsTo != e.to.belongsTo) {
				e.from.belongsTo.merge(e.to.belongsTo);
				e.from.belongsTo.edges.add(e);
				if (DEBUG) {
					System.err.println ("" + testNum + " MST contains " + e);
				}
			}
		}
	}

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main (String[] argv) throws IOException
	{
		BufferedReader in;
		if (argv.length > 0)
		{
			in = new BufferedReader(new FileReader(argv[0]));
		}
		else {
			in = new BufferedReader (new InputStreamReader(System.in));
		}
		run (in);
	}

	

}
