#include<iostream>
#include<iomanip>
#include<vector>
#include<string>
#include<algorithm>
#include<cmath>
#include<functional>
#include<map>
#include<cstdlib>
#include<complex>
#include<numeric>
#include<set>
#include<deque>
#include<queue>

using namespace std;
using Int = long long;
Int INF = 1LL << 60;
const Int MOD = 1000000000 + 7;

template <class T>
ostream& operator<<(ostream& os, const vector<T>& vec) {
	for (const auto& e : vec) os << e << (&e == &vec.back() ? "\n" : " ");
	return os;
}
template <class T>
ostream& operator<<(ostream& os, const set<T>& st) {
	for (const auto& e : st) os << e << " ";
	return os;
}
template <class T1, class T2>
ostream& operator<<(ostream& os, const pair<T1, T2>& p) {
	os << "(" << p.first << "," << p.second << ")";
	return os;
}
template <class T1, class T2>
ostream& operator<<(ostream& os, const map<T1, T2>& mp) {
	for (const auto& e : mp) os << e << " ";
	return os;
}

#ifdef _DEBUG
template <class T>
void dump(const char* str, T&& h) { cerr << str << " = " << h << "\n"; };
template <class Head, class... Tail>
void dump(const char* str, Head&& h, Tail&&... t) {
	while (*str != ',') cerr << *str++; cerr << " = " << h << "\n";
	dump(str + (*(str + 1) == ' ' ? 2 : 1), t...);
}
#define DMP(...) dump(#__VA_ARGS__, __VA_ARGS__)
#else 
#define DMP(...) ((void)0)
#endif

struct edge {
	Int from;
	Int to;
	Int dis;
};

//sort用
bool cmpx(const vector<Int>& a, vector<Int>& b) {
	return a[0] < b[0];
}

bool cmpy(const vector<Int>& a, vector<Int>& b) {
	return a[1] < b[1];
}

//priority_queue用（sortとは符号の向きが反対であることに注意）
bool cmpq(const vector<Int>& a, const vector<Int>& b) {
	return a[2] > b[2];
}

/*使い方
UnionFind a(N);//初期化、N個の頂点を考える

Int b = a.root(x);//頂点xの親が返る
bool c = a.same(x,y);//xとyが同じ集合→trueが返る
Int d = a.size(x);//頂点xの属する集合の要素数が返る

a.unite(x,y);//頂点xとyを連結する
（xとyが元々同じ集合に属している場合falseが、元々異なる集合に属している場合trueが返るようになっている）

*/

class UnionFind {
private:
	vector<Int> data;//data[i]:頂点iの親（頂点番号）

public:
	UnionFind(Int size) : data(size, -1) { }//全ての頂点の親を-1で初期化
	Int root(Int x) { return data[x] < 0 ? x : data[x] = root(data[x]); }//data[i]が負なら頂点i自身が親であり、その時さらに-data[i]は集合の要素数を表す
	bool same(Int x, Int y) { return root(x) == root(y); }//頂点xとyが同じ集合に属するかどうか
	Int size(Int x) { return -data[root(x)]; }//頂点xの含まれる集合の要素数

	bool unite(Int x, Int y) {//頂点xとyを同じ集合に連結する
		x = root(x); y = root(y);
		if (x != y) {//頂点xとyが元々違う集合に属していた場合
			if (data[y] < data[x]) swap(x, y);
			data[x] += data[y]; data[y] = x;
			return true;//連結したという意味でtrueが返る
		}
		else {//頂点xとyが元々同じ集合に属していた場合
			return false;//連結を行わなかったという意味でfalseが返る
		}
	}
};

int main() {

	cin.tie(nullptr);
	ios::sync_with_stdio(false);

	Int N;
	cin >> N;

	vector<vector<Int>> xy(N, vector<Int>(3));//（x座標、y座標、頂点番号）
	for (Int i = 0; i < N; i++) {
		cin >> xy[i][0] >> xy[i][1];
		xy[i][2] = i;
	}

	priority_queue<vector<Int>, vector<vector<Int>>, decltype(&cmpq)> pq(&cmpq);

	//x座標でソート
	sort(xy.begin(), xy.end(), cmpx);
	DMP(xy);

	//x座標の距離
	vector<Int> temp(3);//（from、to、距離）
	for (Int i = 0; i < N - 1; i++) {
		temp[0] = xy[i][2];//from
		temp[1] = xy[i + 1][2];//to
		temp[2] = abs(xy[i][0] - xy[i + 1][0]);
		pq.push(temp);
	}

	//y座標でソート
	sort(xy.begin(), xy.end(), cmpy);
	DMP(xy);


	for (Int i = 0; i < N - 1; i++) {
		temp[0] = xy[i][2];//from
		temp[1] = xy[i + 1][2];//to
		temp[2] = abs(xy[i][1] - xy[i + 1][1]);
		pq.push(temp);
	}

	UnionFind a(N);
	Int ans = 0;
	while (!pq.empty()) {

		temp = pq.top();
		if (a.same(temp[0], temp[1])) {//fromとtoが既につながれている場合スルー
			pq.pop();
			continue;
		}
		else {
			ans += temp[2];//ansに距離を足す
			a.unite(temp[0], temp[1]);//fromとtoをつなげる
			pq.pop();
		}
	}

	//sort(pq.begin(), pq.end(), cmpq);

	//UnionFind a(N);
	//Int ans = 0;
	//Int len = pq.size();
	//for (Int i = 0; i < len; i++) {

	//	//temp = pq.top();
	//	temp = pq[i];
	//	if (a.same(temp[0], temp[1])) {//fromとtoが既につながれている場合スルー
	//		//pq.pop();
	//		continue;
	//	}
	//	else {
	//		ans += temp[2];//ansに距離を足す
	//		a.unite(temp[0], temp[1]);//fromとtoをつなげる
	//		//pq.pop();
	//	}
	//}

	cout << ans << "\n";

}



Measured memory_bytes: 18860