#include<bits/stdc++.h>
#define rep(i, n) for(int i=0; i<n; i++)
#define repo(i, n) for(int i=1; i<=n; i++)
#define INF 1001001001

using namespace std;
using ull = unsigned long long;
using ll = long long;
using P = pair<int, int>;
const int mod = 1000000007;

// 素集合データ構造
struct UnionFind
{
  // par[i]：データiが属する木の親の番号。i == par[i]のとき、データiは木の根ノードである
  vector<int> par;
  // sizes[i]：根ノードiの木に含まれるデータの数。iが根ノードでない場合は無意味な値となる
  vector<int> sizes;

  UnionFind(int n) : par(n), sizes(n, 1) {
    // 最初は全てのデータiがグループiに存在するものとして初期化
    rep(i,n) par[i] = i;
  }

  // データxが属する木の根を得る
  int find(int x) {
    if (x == par[x]) return x;
    return par[x] = find(par[x]);  // 根を張り替えながら再帰的に根ノードを探す
  }

  // 2つのデータx, yが属する木をマージする
  void unite(int x, int y) {
    // データの根ノードを得る
    x = find(x);
    y = find(y);

    // 既に同じ木に属しているならマージしない
    if (x == y) return;

    // xの木がyの木より大きくなるようにする
    if (sizes[x] < sizes[y]) swap(x, y);

    // xがyの親になるように連結する
    par[y] = x;
    sizes[x] += sizes[y];
    // sizes[y] = 0;  // sizes[y]は無意味な値となるので0を入れておいてもよい
  }

  // 2つのデータx, yが属する木が同じならtrueを返す
  bool same(int x, int y) {
    return find(x) == find(y);
  }

  // データxが含まれる木の大きさを返す
  int size(int x) {
    return sizes[find(x)];
  }
};

// 頂点a, bをつなぐコストcostの（無向）辺
struct Edge
{
  int a, b, cost;

  // コストの大小で順序定義
  bool operator<(const Edge& o) const {
    return cost < o.cost;
  }
};

struct vertex
{
  int xx, yy, nn;

  // コストの大小で順序定義
  bool operator<(const vertex& o) const {
    return xx < o.xx;
  }
};

// 頂点数と辺集合の組として定義したグラフ
struct Graph
{
  int n;  // 頂点数
  vector<Edge> es;  // 辺集合

  // クラスカル法で無向最小全域木のコストの和を計算する
  // グラフが非連結のときは最小全域森のコストの和となる
  ll kruskal() {
    // コストが小さい順にソート
    sort(es.begin(), es.end());

    UnionFind uf(n);
    ll min_cost = 0;

    rep(ei, es.size()) {
      Edge& e = es[ei];
      if (!uf.same(e.a, e.b)) {
        // 辺を追加しても閉路ができないなら、その辺を採用する
        min_cost += e.cost;
        uf.unite(e.a, e.b);
      }
    }

    return min_cost;
  }
};
/*
// 標準入力からグラフを読み込む
Graph input_graph() {
  Graph g;
  cin >> g.n;
  vector<P> ver;
  rep(i, g.n) {
    int x, y;
    cin >> x >> y;
    rep(j, ver.size()){
      Edge e;
      e.a=j; e.b=i;
      e.cost=min(abs(ver[j].xx-x), abs(ver[j].yy-y));
      g.es.push_back(e);
    }
    ver.push_back(P(x, y));
  }
  return g;
}
*/

Graph input_graph() {
  Graph g;
  cin >> g.n;
  vector<vertex> ver1;
  vector<vertex> ver2;
  rep(i, g.n) {
    int x, y;
    cin >> x >> y;
    vertex v1, v2;
    v1.xx=x; v1.yy=y; v1.nn=i;
    v2.xx=y; v2.yy=x; v2.nn=i;
    ver1.push_back(v1);
    ver2.push_back(v2);
  }
  sort(ver1.begin(), ver1.end());
  sort(ver2.begin(), ver2.end());
  for(int j=0; j<ver1.size(); j++){
    Edge e, e2;
    if(j<ver1.size()-1){
      e.a=ver1[j].nn; e.b=ver1[j+1].nn;
      e.cost=min(abs(ver1[j].xx-ver1[j+1].xx), abs(ver1[j].yy-ver1[j+1].yy));
      g.es.push_back(e);
    }
    /*
    if(j>0){
      e2.a=ver1[j].nn; e.b=ver1[j-1].nn;
      e2.cost=min(abs(ver1[j].xx-ver1[j-1].xx), abs(ver1[j].yy-ver1[j-1].yy));
      g.es.push_back(e2);
    }
    */
  }
  for(int j=0; j<ver2.size(); j++){
    Edge e, e2;
    if(j<ver2.size()-1){
      e.a=ver2[j].nn; e.b=ver2[j+1].nn;
      e.cost=min(abs(ver2[j].xx-ver2[j+1].xx), abs(ver2[j].yy-ver2[j+1].yy));
      g.es.push_back(e);
    }
    /*
    if(j!=0){
      e2.a=ver2[j].nn; e.b=ver2[j-1].nn;
      e2.cost=min(abs(ver2[j].xx-ver2[j-1].xx), abs(ver2[j].yy-ver2[j-1].yy));
      g.es.push_back(e2);
    }
    */
  }
  return g;
}

int main()
{
  Graph g = input_graph();
  cout << g.kruskal() << endl;
  return 0;
}


Measured memory_bytes: 7212