#include <bits/stdc++.h>

#define TYPE(c)   remove_reference_t<decltype(c)>
#define REP(i, n) for(TYPE(n) i = 0; i < n; i++)
#define FOR(v, c) for(TYPE(c.begin()) v = c.begin(); v != c.end(); v++)
#define ALL(c)    c.begin(), c.end()
#define SORT(c)   sort(ALL(c))
#define RSORT(c)  sort(ALL(c), greater<TYPE(c)::value_type>())
#define UNIQUE(c) c.erase(unique(ALL(c)), c.end())

using namespace std;
using ll = long long;

constexpr int MOD  = (int)1e9 + 7;
constexpr int INF  = (int)1e9 + 1;
constexpr ll  LINF = (ll)1e18 + 1;
template<typename S, typename T> ostream& operator<<(ostream &os, const pair<S, T>& p)
{ return os << "(" << p.first << "," << p.second << ")"; }
template<typename T> auto operator<<(ostream& os, const T& t)
    -> typename conditional<true, ostream&, decltype(*t.begin())>::type
{ os << "["; FOR(v, t) { if(v != t.begin()) os << ","; os << *v; } os << "]"; return os; }

class Graph {
    using ll = long long;
public:
    enum class Type { Directed, Undirected };
    struct Edge {
        int  src, dst;
        ll   cost;
        bool validity;
        Edge(const int& _src, const int& _dst, const ll& _cost = 1) :
            src(_src), dst(_dst), cost(_cost), validity(true) { }
        friend std::ostream& operator <<(std::ostream& os, const Edge& e) {
            os << "[" << e.src << " -> " << e.dst  << "] " << "cost : " << e.cost << ", " << "validity : " << e.validity;
            return os;
        }
        friend std::ostream& operator <<(std::ostream& os, const std::shared_ptr<Edge>& e) {
            os << "[" << e->src << " -> " << e->dst  << "] " << "cost : " << e->cost << ", " << "validity : " << e->validity;
            return os;
        }
    };
    struct Vertex {
        ll cost{(ll)1e9 + 1};
        explicit Vertex(const ll& _cost) : cost(_cost) { }
        friend std::ostream& operator <<(std::ostream& os, const Vertex& v) { os << "cost : " << v.cost; return os; }
    };
    const Type edge_type;
    std::vector<Vertex> vertices;
    std::vector<std::map<int, std::shared_ptr<Edge>>> edges;
    Graph(const int&  num_vertex,
          const Type& edge_type,
          const ll& vertex_init_cost = (ll)1e9 + 1) :
        edge_type(edge_type),
        vertices(num_vertex, Vertex(vertex_init_cost)),
        edges(num_vertex) { }
    void setCostToAllVertex(const ll& val) {
        std::fill(vertices.begin(), vertices.end(), Vertex(val));
    };
    void addEdge(const int& src, const int& dst, const ll& cost = 1) {
        auto edge = std::make_shared<Edge>(src, dst, cost);
        edges[src].emplace(dst, edge);
        if(edge_type == Type::Undirected) edges[dst].emplace(src, edge);
    }
    friend std::ostream& operator <<(std::ostream& os, const Graph& g) {
        os << std::endl
           << "--- Vertex Information ---" << std::endl;
        for(size_t i = 0; i < g.vertices.size(); i++) { os << "[[" << i << "] " << g.vertices[i] << "]" << std::endl; }
        os << "---- Edge Information ----" << std::endl;
        for(const auto& ee : g.edges) { for(const auto& e : ee) { os << "[" << e.second << "]" << std::endl; }}
        os << "--------------------------";
        return os;
    }
};

class UnionFind {
    using ll = long long;
public:
    struct Node {
        ll parent, weight, size, rank;
        Node(const ll& _parent = 0,
             const ll& _weight = 0,
             const ll& _size   = 1,
             const ll& _rank   = 0) :
            parent(_parent),
            weight(_weight),
            size(_size),
            rank(_rank) { }
    };

    explicit UnionFind(const ll& num_node) {
        node_.reserve(num_node);
        for(ll i = 0; i < num_node; i++) {
            node_.emplace_back(i);
        }
    }

    const std::vector<Node>& getNode() const {
        return node_;
    }

    ll searchAndZipRoot(const ll& x) {
        if(node_[x].parent == x) {
            return x;
        }
        else {
            node_[x].weight += node_[node_[x].parent].weight;
            return node_[x].parent = searchAndZipRoot(node_[x].parent);
        }
    }

    void unite(const ll& x,
               const ll& y,
               const ll& weight = 0) {
        auto rx = searchAndZipRoot(x);
        auto ry = searchAndZipRoot(y);
        if(rx != ry) {
            auto yw = weight + calcWeight(x) - calcWeight(y);
            if(node_[rx].rank < node_[ry].rank) {
                std::swap(rx, ry);
                yw *= -1;
            }
            node_[ry].parent  = rx;
            node_[ry].weight  = yw;
            node_[rx].size   += node_[ry].size;
            if(node_[rx].rank == node_[ry].rank) {
                node_[rx].rank++;
            }
        }
    }

    ll calcWeight(const ll& x) {
        searchAndZipRoot(x);
        return node_[x].weight;
    }

    ll calcDiff(const ll& x,
                const ll& y) {
        return std::abs(calcWeight(x) - calcWeight(y));
    }

    ll calcSize(const ll& x) {
        return node_[searchAndZipRoot(x)].size;
    }

    template<class... A>
    bool checkWhetherRootIsSame(const A&... nodes) {
        auto node_list = std::initializer_list<ll>{nodes...};
        if(node_list.size() < 2) return false;
        auto v = searchAndZipRoot(*node_list.begin());
        for(size_t i = 1; i < node_list.size(); i++) {
            if(v != searchAndZipRoot(*(node_list.begin() + i))) return false;
        }
        return true;
    }

private:
    std::vector<Node> node_;
};

/**
 *  最小全域木を求める(Kruskal法)
 *  NOTE: 計算量O(|E|log|V|), Dependencies: comp-Graph,
 *  @Return: 最小全域木を返す グラフが非連結の場合、連結成分ごとに最小全域木を構成する
 */
// 使用例
// auto mst = kruskal(g);
// std::cout << mst.getTotalCost() << std::endl;
Graph kruskal(const Graph& g) {
    if(g.edge_type != Graph::Type::Undirected) throw std::logic_error("Graph type is invalid");
    Graph mst(g.vertices.size(), Graph::Type::Undirected);
    std::vector<Graph::Edge> mst_e;
    mst_e.reserve(g.edges.size());
    for(const auto& ee : g.edges) for(const auto& e : ee) mst_e.emplace_back(*e.second.get());
    auto sort_rule = [](const Graph::Edge& l, const Graph::Edge& r) { return l.cost < r.cost; };
    sort(mst_e.begin(), mst_e.end(), sort_rule);
    UnionFind uf(g.vertices.size());
    for(const auto& e : mst_e) {
        if(!uf.checkWhetherRootIsSame(e.src, e.dst)) {
            mst.addEdge(e.src, e.dst, e.cost);
            uf.unite(e.src, e.dst);
        }
    }
    return mst;
}

void DFS(Graph&     g,
         const int& index,
         ll&       ans) {
    for(const auto& e : g.edges[index]) {
        if(g.vertices[e.first].cost == 0) {
            g.vertices[e.first].cost = 1;
            ans += e.second->cost;
            DFS(g, e.first, ans);
        }
    }
}

int main() {
    cin.tie(0);
    ios::sync_with_stdio(false);

    int N;
    std::cin >> N;

    vector<pair<pair<int, int>, int>> city_prior_x;
    vector<pair<pair<int, int>, int>> city_prior_y;
    REP(i, N) {
        int x, y;
        std::cin >> x >> y;
        city_prior_x.emplace_back(make_pair(x, y), i);
        city_prior_y.emplace_back(make_pair(y, x), i);
    }

    Graph g(N, Graph::Type::Undirected);

    sort(ALL(city_prior_x));
    sort(ALL(city_prior_y));
    for(int i = 0; i < N - 1; i++) {
        g.addEdge(city_prior_x[i].second,
                  city_prior_x[i + 1].second,
                  min(abs(city_prior_x[i].first.first  - city_prior_x[i + 1].first.first),
                      abs(city_prior_x[i].first.second - city_prior_x[i + 1].first.second)));
        g.addEdge(city_prior_y[i].second,
                  city_prior_y[i + 1].second,
                  min(abs(city_prior_y[i].first.first  - city_prior_y[i + 1].first.first),
                      abs(city_prior_y[i].first.second - city_prior_y[i + 1].first.second)));
    }

    auto mst = kruskal(g);

    mst.setCostToAllVertex(0);
    mst.vertices[0].cost = 1;
    ll ans = 0;
    DFS(mst, 0, ans);

    std::cout << ans << std::endl;

    return 0;
}


Measured memory_bytes: 77620