use std::io::{stdin, Read, StdinLock};
use std::str::FromStr;

fn main() {
    let cin = stdin();
    let mut scan = Scanner::new(cin.lock());
    let n = scan.read::<usize>();
    let h = scan.read::<usize>();
    let w = scan.read::<usize>();
    let mut edges = vec![];
    for _ in 0..n {
        let r = scan.read::<usize>()-1;
        let c = scan.read::<usize>()-1;
        let a = scan.read::<i64>();
        edges.push((a, r ,c));
    }
    edges.sort();
    edges.reverse();
    let mut utree = Utree::new(h+w+1);
    let pole = h+w;
    let mut ret = 0;
    for &(a, r, c) in &edges {
        if utree.is_same(r, c+h) {
            if !utree.is_same(r, pole) && !utree.is_same(c+h, pole) {
                utree.unite(r, c+h);
                utree.unite(r, pole);
                ret += a;
            } else if !(utree.is_same(r, pole) && utree.is_same(c+h, pole)) {
                utree.unite(r, c+h);
                ret += a;
            }
        } else {
            utree.unite(r, c+h);
            ret += a;
        }
    }
    println!("{}", ret);
}

#[allow(dead_code)]
pub struct Utree {
    n: usize,
    par: Vec<usize>,
    rank: Vec<usize>,
    size: Vec<usize>,
}


#[allow(dead_code)]
impl Utree {
    pub fn new(n: usize) -> Utree {
        Utree {
            n: n,
            par: (0..n).collect(),
            rank: vec![0; n],
            size: vec![1; n],
        }
    }

    fn find(&mut self, x: usize) -> usize {
        if self.par[x] == x {
            x
        } else {
            let parent = { self.par[x] };
            let ret = self.find(parent);
            self.par[x] = ret;
            ret
        }
    }

    pub fn unite(&mut self, mut x: usize, mut y: usize) {
        x = self.find(x);
        y = self.find(y);
        if x == y {
            return;
        };

        if self.rank[x] < self.rank[y] {
            self.par[x] = y;
            self.size[y] += self.size[x];
        } else {
            self.par[y] = x;
            self.size[x] += self.size[y];
            if self.rank[x] == self.rank[y] {
                self.rank[x] += 1;
            }
        }
    }

    pub fn is_same(&mut self, mut x: usize, mut y: usize) -> bool {
        x = self.find(x);
        y = self.find(y);
        x == y
    }

    fn get_size(&mut self, mut x: usize) -> usize {
        x = self.find(x);
        self.size[x]
    }

    fn size(&mut self) -> usize {
        let mut hset = std::collections::HashSet::new();
        for i in 0..self.n {hset.insert(self.find(i));}
        hset.len()
    }
}


// region template
#[allow(dead_code)]
fn iu(i: isize) -> usize { i as usize }

#[allow(dead_code)]
fn ui(i: usize) -> isize { i as isize }

#[allow(dead_code, deprecated)]
fn join<T: std::fmt::Display>(slice: &[T], sep: &str) -> String {
    let strings = slice.iter().map(|t| format!("{}", t)).collect::<Vec<_>>();
    strings.connect(sep)
}

#[allow(dead_code)]
fn arr<'a, S, T>(n: usize, mut f: S) -> Vec<T> where S: FnMut(usize) -> T + 'a {
    (0..n).map(|i| f(i)).collect::<Vec<T>>()
}

struct Scanner<'a> {
    cin: StdinLock<'a>,
}

#[allow(dead_code)]
impl<'a> Scanner<'a> {
    fn new(cin: StdinLock<'a>) -> Scanner<'a> {
        Scanner { cin: cin }
    }

    fn read1<T: FromStr>(&mut self) -> Option<T> {
        let token = self.cin.by_ref().bytes().map(|c| c.unwrap() as char)
            .skip_while(|c| c.is_whitespace())
            .take_while(|c| !c.is_whitespace())
            .collect::<String>();
        token.parse::<T>().ok()
    }

    fn read<T: FromStr>(&mut self) -> T {
        self.read1().unwrap()
    }

    fn readn<T: FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.read::<T>()).collect()
    }

    fn chars(&mut self) -> Vec<char> {
        self.read::<String>().chars().collect()
    }
}
// endregion

Measured memory_bytes: 15480