#[allow(unused_imports)]
use std::cmp::{max, min, Ordering};
#[allow(unused_imports)]
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
#[allow(unused_imports)]
use std::io::{stdin, stdout, BufWriter, Write};
#[allow(unused_imports)]
use std::iter::FromIterator;
#[allow(unused_macros)]
macro_rules ! input { ( source = $ s : expr , $ ( $ r : tt ) * ) => { let mut iter = $ s . split_whitespace ( ) ; input_inner ! { iter , $ ( $ r ) * } } ; ( $ ( $ r : tt ) * ) => { let mut s = { use std :: io :: Read ; let mut s = String :: new ( ) ; std :: io :: stdin ( ) . read_to_string ( & mut s ) . unwrap ( ) ; s } ; let mut iter = s . split_whitespace ( ) ; input_inner ! { iter , $ ( $ r ) * } } ; }
#[allow(unused_macros)]
macro_rules ! input_inner { ( $ iter : expr ) => { } ; ( $ iter : expr , ) => { } ; ( $ iter : expr , $ var : ident : $ t : tt $ ( $ r : tt ) * ) => { let $ var = read_value ! ( $ iter , $ t ) ; input_inner ! { $ iter $ ( $ r ) * } } ; }
#[allow(unused_macros)]
macro_rules ! read_value { ( $ iter : expr , ( $ ( $ t : tt ) ,* ) ) => { ( $ ( read_value ! ( $ iter , $ t ) ) ,* ) } ; ( $ iter : expr , [ $ t : tt ; $ len : expr ] ) => { ( 0 ..$ len ) . map ( | _ | read_value ! ( $ iter , $ t ) ) . collect ::< Vec < _ >> ( ) } ; ( $ iter : expr , chars ) => { read_value ! ( $ iter , String ) . chars ( ) . collect ::< Vec < char >> ( ) } ; ( $ iter : expr , usize1 ) => { read_value ! ( $ iter , usize ) - 1 } ; ( $ iter : expr , $ t : ty ) => { $ iter . next ( ) . unwrap ( ) . parse ::<$ t > ( ) . expect ( "Parse error" ) } ; }
#[allow(unused_macros)]
macro_rules ! debug { ( $ ( $ a : expr ) ,* ) => { eprintln ! ( concat ! ( $ ( stringify ! ( $ a ) , " = {:?}, " ) ,* ) , $ ( $ a ) ,* ) ; } }
#[doc = " https://github.com/hatoo/competitive-rust-snippets"]
const BIG_STACK_SIZE: bool = true;
#[allow(dead_code)]
fn main() {
    use std::thread;
    if BIG_STACK_SIZE {
        thread::Builder::new()
            .stack_size(32 * 1024 * 1024)
            .name("solve".into())
            .spawn(solve)
            .unwrap()
            .join()
            .unwrap();
    } else {
        solve();
    }
}
struct WeightedUnionFind {
    par: Vec<usize>,
    rank: Vec<usize>,
    diff_weight: Vec<i32>,
}
impl WeightedUnionFind {
    fn new(n: usize) -> WeightedUnionFind {
        WeightedUnionFind {
            par: (0..n).collect::<Vec<usize>>(),
            rank: vec![0; n], diff_weight: vec![0; n],
        }
    }

    fn root(&mut self, x: usize) -> usize {
        if (self.par[x] == x) {
             return x;
        } else {
            let y = self.par[x];
            let z = self.root(y);
            self.diff_weight[x] += self.diff_weight[y];
            self.par[x] = z;
            return z;
        }
    }

    fn weight(&mut self, x: usize) -> i32 {
        self.root(x);
        self.diff_weight[x]
    }

    fn same(&mut self, x: usize, y: usize) -> bool {
        self.root(x) == self.root(y)
    }

    fn merge(&mut self, x: usize, y: usize, w: i32) -> bool {
        let mut w = w;
        w += self.weight(x);
        w -= self.weight(y);

        let mut a = self.root(x);
        let mut b = self.root(y);

        if a == b {
            return false;
        }

        if self.rank[a] < self.rank[b] {
            std::mem::swap(&mut a, &mut b);
            w = -w;
        }
        assert!(self.rank[a] >= self.rank[b]);

        if self.rank[a] == self.rank[b] {
            self.rank[a] += 1;
        }

        self.par[b] = a;
        self.diff_weight[b] = w;

        return true
    }
}
fn solve() {
    input! {
        n: usize,
        m: usize,
        dist: [(usize, usize, i32); m],
    };

    let mut wuf = WeightedUnionFind::new(n+1);

    for &(l,r,d) in &dist {
        if wuf.same(l,r) {
            if wuf.weight(r) - wuf.weight(l) != d {
                println!("No"); return;
            }
        } else {
            wuf.merge(l,r,d);
        }
    }

    println!("Yes");
}

Measured memory_bytes: 20600