use std::io;
use std::str::FromStr;
use std::collections::BinaryHeap;
use std::cmp::Ordering;

type Pair = (usize, usize);

fn input<T:FromStr>() -> T {
    let mut line: String = String::new();
    io::stdin().read_line(&mut line);
    line.trim().parse().ok().unwrap()
}

fn input_tuple() -> (usize, usize) {
    let mut line: String = String::new();
    io::stdin().read_line(&mut line);
    let vec: Vec<usize> = line.split_whitespace().map(|s| s.parse().unwrap()).collect();
    (vec[0], vec[1])
}

#[derive(Eq, Clone, Debug)]
struct Edge {
    from: usize,
    to: usize,
    cost: i64,
}

impl Ord for Edge {
    fn cmp(&self, other: &Self) -> Ordering {
        self.cost.cmp(&other.cost)
    }
}

impl PartialOrd for Edge {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for Edge {
    fn eq(&self, other: &Self) -> bool {
        self.cost == other.cost
    }
}

#[derive(Debug, PartialEq, Clone)]
struct Node {
    parent: Option<usize>, 
    size: usize,
}

#[derive(Debug, PartialEq)]
pub struct UnionFind {
    tree: Vec<Node>,
}

impl UnionFind {
    pub fn new(n: usize) -> UnionFind {
        UnionFind {tree: vec![Node {parent: None, size: 1}; n]}
    }

    pub fn unite(&mut self, i: usize, j: usize) {
        let root_i = self.find(i);
        let root_j = self.find(j);
        if root_i == root_j {
            return
        }

        if self.tree[root_i].size < self.tree[root_j].size {
            self.tree[root_i].size += self.tree[root_j].size;
            self.tree[root_j].parent = Some(root_i);
        } else {
            self.tree[root_j].size += self.tree[root_i].size;
            self.tree[root_i].parent = Some(root_j);
        }

    }

    pub fn find(&mut self, i: usize) -> usize {
        match self.tree[i].parent {
            Some(p) => {
                let root = self.find(p);
                self.tree[i].parent = Some(root);
                root
            },
            None => i
        }
    }

    pub fn equiv(&mut self, i: usize, j: usize) -> bool {
        self.find(i) == self.find(j)
    }

    pub fn size(&mut self, i: usize) -> usize {
        let root = self.find(i);
        self.tree[root].size
    }
}

fn kruskal(n: usize, x_dist: &mut BinaryHeap<Edge>, y_dist: &mut BinaryHeap<Edge>) -> i64 {
    let mut union_find = UnionFind::new(n);
    let mut cost = 0;
    while x_dist.len() > 0 || y_dist.len() > 0 {
        let edge: Edge;
        if x_dist.is_empty() {
            edge = y_dist.pop().unwrap();
        } else if y_dist.is_empty() {
            edge = x_dist.pop().unwrap();
        } else {
            if x_dist.peek().unwrap().cost < y_dist.peek().unwrap().cost {
                edge = y_dist.pop().unwrap()
            } else {
                edge = x_dist.pop().unwrap();
            }
        }
        // println!("{:?}", edge);
        
        if !union_find.equiv(edge.from, edge.to) {
            cost += edge.cost;
            union_find.unite(edge.from, edge.to);
        }
    }
    cost
}

fn main() {
    let n: usize = input();
    let mut x_pos: Vec<Pair> = Vec::new();
    let mut y_pos: Vec<Pair> = Vec::new();
    let mut x_dist: BinaryHeap<Edge> = BinaryHeap::new();
    let mut y_dist: BinaryHeap<Edge> = BinaryHeap::new();
    for i in 0..n {
        let (x, y) = input_tuple();
        x_pos.push((i, x)); y_pos.push((i, y));
    }
    x_pos.sort_by(|a, b| a.1.cmp(&b.1));
    y_pos.sort_by(|a, b| a.1.cmp(&b.1));

    for i in 0..(n-1) {
        x_dist.push(Edge{from: x_pos[i].0, to: x_pos[i+1].0, cost: x_pos[i].1 as i64 - x_pos[i+1].1 as i64});
        y_dist.push(Edge{from: y_pos[i].0, to: y_pos[i+1].0, cost: y_pos[i].1 as i64 - y_pos[i+1].1 as i64});
    }

    // println!("{:?}", x_dist);
    // println!("{:?}", y_dist);

    println!("{}", -kruskal(n, &mut x_dist, &mut y_dist));

}


Measured memory_bytes: 25416