#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;

	bool operator>(const edge& r) const { return this->dis > r.dis; }

};

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];
}

bool cmpq(const edge& a, const edge& b) {
	return a.dis < b.dis;
}

/*使い方
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が返る
		}
	}
};

template<class T = Int>
struct Edge {
	Int to;
	T cost;
	Edge() {}
	Edge(Int to, T cost) : to(to), cost(cost) {}
	bool operator>(const Edge& r) const { return this->cost > r.cost; }
};

template<class T>
vector<T> Dijkstra(vector<vector<Edge<T>>>& edges, Int st) {

	Int V = (Int)edges.size();
	const T inf = numeric_limits<T>::max() / 2;
	vector<T> cost(V, inf);
	cost[st] = 0;

	priority_queue<Edge<T>, vector<Edge<T>>, greater<Edge<T>>> p_que;//要素の型はEdge<T>、内部コンテナはvector<Edge>>、昇順
	p_que.emplace(st, cost[st]);

	while (!p_que.empty()) {

		Edge<T> now(p_que.top().to, p_que.top().cost);//now.to: 配る側になった頂点 now.cost: その頂点の最小コスト
		p_que.pop();

		//↓この一行、有向グラフ（行きと帰りで辺の重みが違う場合）で威力を発揮、オーダーレベルで計算量が異なることも
		//後からキューに入れたものが追い越した場合、追い越されたものはオワコンになる
		if (cost[now.to] < now.cost) continue;


		for (const Edge<T>& e : edges[now.to]) {
			T tmp_cost = now.cost + e.cost;
			if (cost[e.to] > tmp_cost) {
				cost[e.to] = tmp_cost;
				p_que.emplace(e.to, cost[e.to]);
			}
		}
	}

	return cost; // min cost to vertex idx from st
}

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;
	}

	auto cmp = [](edge& a, edge& b) {
		return a.dis > b.dis;
	};
	priority_queue<edge, vector<edge>, decltype(cmp)> pq(cmp);
	//priority_queue<edge> pq(cmpq);
	//priority_queue<edge,vector<edge>,greater<edge>> pq;
	//vector<vector<Int>> pq;


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

	//x座標の距離
	edge temp;//（from、to、距離）
	for (Int i = 0; i < N - 1; i++) {
		temp.from = xy[i][2];//from
		temp.to = xy[i + 1][2];//to
		temp.dis = 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.from = xy[i][2];//from
		temp.to = xy[i + 1][2];//to
		temp.dis = 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.from,temp.to)) {//fromとtoが既につながれている場合スルー
			pq.pop();
			continue;
		}
		else {
			ans += temp.dis;//ansに距離を足す
			a.unite(temp.from, temp.to);//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: 14064