// 克鲁斯卡尔算法
// 计算任意两点间的距离,并记录每条边,编号【0 - n-1】
// 对于边的集合进行排序,从小到大
// (克鲁斯卡尔算法)依次遍历所有边,使用并查集判断是否处于同一集合,不属于一个集合就记录该边
// 当连通分量=1时,输出
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
struct Dot
{
int id; // 编号,re0
double x;
double y;
};
struct Edge
{
int from;
int to;
double length;
};
vector<Dot> dot;
vector<Edge> edge;
// 并查集 start
const int maxn = 100;
int father[maxn];
int height[maxn];
void init(int n)
{
for (int i = 0; i < n; i++)
{
father[i] = i;
height[i] = 0;
}
}
int find(int x)
{
if (x != father[x])
{
father[x] = find(father[x]);
}
return father[x];
}
void Union(int x, int y)
{
x = find(x);
y = find(y);
if (x != y)
{
if (height[x] < height[y])
{
father[x] = y;
}
else if (height[x] > height[y])
{
father[y] = x;
}
else
{
father[y] = x;
height[x]++;
}
}
}
int countFather(int n)
{
int count = 0;
for (int i = 0; i < n; i++)
{
if (find(i) == i)
count++;
}
return count;
}
// 并查集 end
bool comp(Edge x,Edge y){
return x.length<y.length;
}
int n,num_of_edge;//结点数 边数
int main(){
while (cin>>n)
{
if(n==0) break;
double x,y;
for(int i=0;i<n;i++){
cin>>x>>y;
Dot d;
d.id=i; //进入vector顺序即编号,可不用id
d.x=x;
d.y=y;
dot.push_back(d);
}//处理输入
//每两个结点构成一条边
num_of_edge=n*(n-1)/2;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
Edge e;
e.from=i;
e.to=j;
e.length=sqrt((dot[i].x-dot[j].x)*(dot[i].x-dot[j].x)+(dot[i].y-dot[j].y)*(dot[i].y-dot[j].y));
edge.push_back(e);
}
}
//对边从小到大排序
sort(edge.begin(),edge.end(),comp);
init(n);
double ans=0;
for(int i=0;i<edge.size();i++){
if(find(edge[i].from) != find(edge[i].to)){
Union(edge[i].from , edge[i].to);
ans+=edge[i].length;
}
if(countFather(n)==1){
break;
}
}//kruskal算法
printf("%.2f",ans);
}
}