#include "bits/stdc++.h"
using namespace std;
#define repr(i,a,b) for(int i=(int)(a);i<(int)(b);i++)
#define rep(i,n) repr(i,0,n)
#define reprrev(i,a,b) for(int i=(int)(b)-1;i>=(int)(a);i--)
#define reprev(i,n) reprrev(i,0,n)
#define all(a) (a).begin(),(a).end()
#define rall(a) (a).rbegin(),(a).rend()
#define mp make_pair
#define mt make_tuple
#define MOD 1000000007
#define PI 3.1415926535
typedef long long ll;
typedef pair<int, int> Pi;
typedef pair<ll, ll> Pll;
typedef vector<int> vi;
typedef vector<ll> vll;
typedef vector<bool> vb;
typedef vector<char> vc;
typedef vector<string> vs;
typedef vector<double> vd;
typedef vector<Pi> vPi;
typedef vector<Pll> vPll;
typedef vector<vector<int>> vvi;
typedef vector<vector<bool>> vvb;
typedef vector<vector<ll>> vvll;
typedef vector<vector<char>> vvc;
typedef vector<vector<string>> vvs;
typedef vector<vector<double>> vvd;
typedef vector<vector<Pi>> vvPi;
typedef priority_queue<int, vector<int>, greater<int>> pqli;
typedef priority_queue<ll, vector<ll>, greater<ll>> pqlll;
typedef priority_queue<Pi, vector<Pi>, greater<Pi>> pqlP;
string YN(bool y) { return (y?"YES":"NO"); }
string yn(bool y) { return (y?"Yes":"No"); }
string ON(bool y) { return (y?"OK":"NG"); }
// 素集合データ構造
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 Graph
{
	int n;  //頂点数
	vector<Edge> es;  //辺集合

					  //クラスカル法で無向最小全域木のコストの和を計算する
					  //グラフが非連結のときは最小全域森のコストの和となる

	int kruskal(){
		//コストが小さい順にソート
		sort(es.begin(), es.end());

		UnionFind uf(n);
		int 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;
	}
};
int main(void)
{
	int n;

	cin>>n;

	vector<tuple<int, int, int>> x(n), y(n);
	
	rep(i, n){
		int a, b;
		cin>>a>>b;
		a--; b--;
		x[i]=make_tuple(a, b, i);
		y[i]=make_tuple(b, a, i);
	}
	sort(all(x)); sort(all(y));

	Graph g;

	g.n=n;

	rep(i, n-1){
		Edge e;
		e.a=get<2>(x[i]); e.b=get<2>(x[i+1]); e.cost=min(abs(get<0>(x[i])-get<0>(x[i+1])), abs(get<1>(x[i])-get<1>(x[i+1])));
		g.es.push_back(e);
		e.a=get<2>(y[i]); e.b=get<2>(y[i+1]); e.cost=min(abs(get<0>(y[i])-get<0>(y[i+1])), abs(get<1>(y[i])-get<1>(y[i+1])));
		g.es.push_back(e);
	}
	cout<<g.kruskal()<<endl;
}

Measured memory_bytes: 7284