// Original C++ solution (slower):
#include <iostream>

#include <queue>

#include <vector>

#include <algorithm>

using namespace std;



struct point {

    int x;

    int y;

    int id;

};



struct edge {

    int node;

    int cost;

    bool operator<(const edge& r) const {

        return cost > r.cost;

    }

};



bool cmpx(const point& l, const point& r) {

    return l.x < r.x;

}

bool cmpy(const point& l, const point& r) {

    return l.y < r.y;

}



vector<edge> edges[100010];

bool visited[100010];



int main() {

    int N;

    cin >> N;



    vector<point> v;

    for (int i=0; i<N; i++) {

        int x,y;

        cin >> x >> y;

        v.push_back( point{x,y,i} );

    }



    // x軸で移動するとき

    sort(v.begin(), v.end(), cmpx);

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

        int c = abs(v[i].x - v[i-1].x);

        edges[v[i].id].push_back( edge{v[i-1].id, c} );

        edges[v[i-1].id].push_back( edge{v[i].id, c} );

    }



    // y軸で移動するとき

    sort(v.begin(), v.end(), cmpy);

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

        int c = abs(v[i].y - v[i-1].y);

        edges[v[i].id].push_back( edge{v[i-1].id, c });

        edges[v[i-1].id].push_back( edge{v[i].id, c} );

    }



    int ans = 0;

    priority_queue<edge> q;

    q.push( edge{0, 0} ); // 初期ノード

    while (!q.empty()) {

        edge curr = q.top();

        q.pop();

        if (visited[curr.node]) continue;

        visited[curr.node] = true;

        ans += curr.cost;



        for (int i=0; i<edges[curr.node].size(); i++) {

            edge e = edges[curr.node][i];

            if (visited[e.node]) continue;

            q.push( e );

        }

    }



    cout << ans << endl;



    return 0;

}


// Optimized solution to the same problem (faster):
#include <iostream>

#include <queue>

#include <vector>

#include <algorithm>

using namespace std;



struct UnionFind {

    vector<int> data;

    UnionFind(int size) : data(size, -1) { }

    int findRoot(int x) {

        return data[x] < 0 ? x : data[x] = findRoot(data[x]);

    }

    void uniteSet(int x, int y) {

        x = findRoot(x), y = findRoot(y);

        if (x != y) {

            if (data[x] < data[y]) swap(x,y);

            data[x] += data[y];

            data[y] = x;

        }

    }

    int size(int x) {

        return -data[findRoot(x)];

    }

    bool same(int x, int y) {

        return findRoot(x) == findRoot(y);

    }

};



// 頂点u, vを繋ぐ重みweightの無向辺

struct Edge {

    int u;

    int v;

    int weight;

    bool operator< (const Edge& a) const {

        return weight != a.weight ? weight < a.weight :

            u != a.u ? u < a.u : v < a.v;

    }

};



int kruskal(int n, vector<Edge>& edges) {

    UnionFind uf(n);

    sort(edges.begin(), edges.end());



    int min_cost = 0;

    for (auto e: edges) {

        if (!uf.same(e.u, e.v)) {

            min_cost += e.weight;

            uf.uniteSet(e.u, e.v);

        }

    }

    return min_cost;

}



struct point {

    int x;

    int y;

    int id;

};



bool cmpx(const point& l, const point& r) {

    return l.x < r.x;

}

bool cmpy(const point& l, const point& r) {

    return l.y < r.y;

}



int main() {

    int N;

    cin >> N;



    vector<point> v;

    for (int i=0; i<N; i++) {

        int x,y;

        cin >> x >> y;

        v.push_back( point{x,y,i} );

    }



    vector<Edge> edges;



    // x軸で移動するとき

    sort(v.begin(), v.end(), cmpx);

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

        int c = abs(v[i].x - v[i-1].x);

        edges.push_back( Edge{v[i-1].id, v[i].id, c} );

    }



    // y軸で移動するとき

    sort(v.begin(), v.end(), cmpy);

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

        int c = abs(v[i].y - v[i-1].y);

        edges.push_back( Edge{v[i-1].id, v[i].id, c} );

    }



    cout << kruskal(N+1, edges) << endl;



    return 0;

}


// Fastest known solution for this problem: