Problem:
Score : 500 points

Problem Statement

There are N cards placed face down in a row. On each card, an integer 1 or 2 is written.

Let A_i be the integer written on the i-th card.

Your objective is to guess A_1, A_2, ..., A_N correctly.

You know the following facts:

For each i = 1, 2, ..., M, the value A_{X_i} + A_{Y_i} + Z_i is an even number.

You are a magician and can use the following magic any number of times:

Magic: Choose one card and know the integer A_i written on it. The cost of using this magic is 1.

What is the minimum cost required to determine all of A_1, A_2, ..., A_N?

It is guaranteed that there is no contradiction in given input.

Constraints

All values in input are integers.

2 \leq N \leq 10^5

1 \leq M \leq 10^5

1 \leq X_i < Y_i \leq N

1 \leq Z_i \leq 100

The pairs (X_i, Y_i) are distinct.

There is no contradiction in input. (That is, there exist integers A_1, A_2, ..., A_N that satisfy the conditions.)

Input

Input is given from Standard Input in the following format:

N M
X_1 Y_1 Z_1
X_2 Y_2 Z_2
\vdots
X_M Y_M Z_M

Output

Print the minimum total cost required to determine all of A_1, A_2, ..., A_N.

Sample Input 1

3 1
1 2 1

Sample Output 1

2

You can determine all of A_1, A_2, A_3 by using the magic for the first and third cards.

Sample Input 2

6 5
1 2 1
2 3 2
1 3 3
4 5 4
5 6 5

Sample Output 2

2

Sample Input 3

100000 1
1 100000 100

Sample Output 3

99999

Solution in Java8 (OpenJDK 1.8.0) (judge verdict: Accepted)

import java.io.*;
import java.util.*;

public class Main {
    private boolean eof;
    private BufferedReader br;
    private StringTokenizer st;
    private static PrintWriter out;

    public static void main(String[] args) throws IOException {
        new Main().run();
    }

    private String nextToken() {
        while (st == null || !st.hasMoreTokens()) {
            try {
                st = new StringTokenizer(br.readLine());
            } catch (Exception e) {
                eof = true;
                return "-1";
            }
        }
        return st.nextToken();
    }

    private int nextInt() {
        return Integer.parseInt(nextToken());
    }

    private long nextLong() {
        return Long.parseLong(nextToken());
    }

    private double nextDouble() {
        return Double.parseDouble(nextToken());
    }

    private String nextLine() throws IOException {
        return br.readLine();
    }


    private void run() throws IOException {
        InputStream input = System.in;
        PrintStream output = System.out;
        try {
            File f = new File("a.in");
            if (f.exists() && f.canRead()) {
                input = new FileInputStream(f);
                output = new PrintStream("a.out");
            }
        } catch (Throwable ignored) {
        }
        br = new BufferedReader(new InputStreamReader(input));
        out = new PrintWriter(new PrintWriter(output));
        solve();
        br.close();
        out.close();
    }

    private static boolean[] color = new boolean[100100];
    private static boolean[] used = new boolean[100100];
    static ArrayList<ArrayList<Integer>> a = new ArrayList<>();

    private static void dfs(int x) {
        used[x] = true;
        for (int i = 0; i < a.get(x).size(); i++) {
            if (!used[a.get(x).get(i)]) dfs(a.get(x).get(i));
        }
    }

    private void solve() {
        int n = nextInt();
        int m = nextInt();

        Arrays.fill(used, false);

        for (int i = 0; i <= n; i++) {
            a.add(new ArrayList<>());
        }
        for (int i = 0; i < m; i++) {
            int x = nextInt();
            int y = nextInt();
            int cost = nextInt();
            a.get(x).add(y);
            a.get(y).add(x);
        }

        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if (!used[i]) {
                dfs(i);
                ans++;
            }
        }

        out.print(ans);
    }


    private static Random rnd = new Random();

    //Дерево поиска по ключу, балансится за счет случайных приоритетов
    //Поддерживает сумму(мин,макс) элементов в поддереве и нахождение суммы(мин,макс)на заданном отрезке.
    //
    public static class Node {
        public long key;
        public int prior;
        public long sum;
        public long min;
        public long max;
        public int cnt;
        public int f;
        public Node left, right;

        Node(long key) {
            this.key = key;
            this.prior = rnd.nextInt();
            left = right = null;
            this.sum = key;
            this.min = key;
            this.max = key;
            this.cnt = 1;
            this.f = 1;
        }
    }

    public static long getMin(Node t) {
        return t == null ? Integer.MAX_VALUE : t.min;
    }

    public static long getMax(Node t) {
        return t == null ? Integer.MIN_VALUE : t.max;
    }

    public static long getSum(Node t) {
        return t == null ? 0 : t.sum;
    }

    public static int getCnt(Node t) {
        return t == null ? 0 : t.cnt;
    }

    public static int getF(Node t) {
        return t == null ? 1 : t.f;
    }

    public static void update(Node t) {
        if (t != null) {
            t.sum = t.key + getSum(t.left) + getSum(t.right);
            t.min = Math.min(t.key, getMin(t.left));
            t.max = Math.max(t.key, getMax(t.right));
            t.cnt = 1 + getCnt(t.left) + getCnt(t.right);
        }
    }

    public static void push(Node t) {
        if (t != null && t.f == -1) {
            t.key *= t.f;
            if (t.left != null) t.left.f *= t.f;
            if (t.right != null) t.right.f *= t.f;
            Node c = t.left;
            t.left = t.right;
            t.right = c;
            t.f = 1;
        }
    }

    public static void pushAll(Node t) {
        if (t != null) {
            push(t);
            if (t.left != null) {
                pushAll(t.left);
            }
            if (t.right != null) {
                pushAll(t.right);
            }
        }
    }

    // Все с ключем < key в первое дерево, остальные во второе
    public static Pair<Node, Node> split(Node t, long key) {
        if (t == null) return new Pair<>(null, null);
        push(t);
        if (key > t.key) {
            Pair<Node, Node> p = split(t.right, key);
            t.right = p.first;
            update(t);
            return new Pair<>(t, p.second);
        } else {
            Pair<Node, Node> p = split(t.left, key);
            t.left = p.second;
            update(t);
            return new Pair<>(p.first, t);
        }
    }

    //SPLIT ПО НЕЯВНОМУ КЛЮЧУ
    //add for root = 0
    public static Pair<Node, Node> split(Node t, long key, int add) {
        if (t == null) return new Pair<>(null, null);
        int curKey = add + getCnt(t.left);
        if (key > curKey) { //идем в правое поддерево
            Pair<Node, Node> p = split(t.right, key, curKey + 1);
            t.right = p.first;
            update(t);
            return new Pair<>(t, p.second);
        } else { //идем в левое поддерево
            Pair<Node, Node> p = split(t.left, key, add);
            t.left = p.second;
            update(t);
            return new Pair<>(p.first, t);
        }
    }

    //Все ключи в первом дереве должны быть меньше ключей во втором
    public static Node merge(Node t1, Node t2) {
        if (t1 == null) return t2;
        if (t2 == null) return t1;
        push(t1);
        push(t2);
        if (t1.prior > t2.prior) {
            t1.right = merge(t1.right, t2);
            update(t1);
            return t1;
        } else {
            t2.left = merge(t1, t2.left);
            update(t2);
            return t2;
        }
    }

//    public static Node insert(Node t, Node newNode) {
//        if (t == null) {
//            t = newNode;
//        } else if (newNode.prior > t.prior) {
//            Pair<Node, Node> p = split(t, newNode.key);
//            newNode.left = p.first;
//            newNode.right = p.second;
//            t = newNode;
//        } else {
//            if (newNode.key < t.key) {
//                t.left = insert(t.left, newNode);
//            } else {
//                t.right = insert(t.right, newNode);
//            }
//        }
//        update(t);
//        return t;
//    }

    //Еще один insert, но вроде не такой быстрый
    public static Node insert(Node t, Node newNode) {
        if (t == null) {
            t = newNode;
        } else {
            Pair<Node, Node> p = split(t, newNode.key);
            t = merge(p.first, newNode);
            t = merge(t, p.second);
        }
        update(t);
        return t;
    }

    //Объединяет 2 разных дерева
    public static Node unite(Node t1, Node t2) {
        if (t1 == null) return t2;
        if (t2 == null) return t1;
        push(t1);
        push(t2);
        if (t1.prior < t2.prior) {
            Node c = t1;
            t1 = t2;
            t2 = c;
        }
        Pair<Node, Node> split1 = split(t2, t1.key);
        t1.left = unite(t1.left, split1.first);
        t1.right = unite(t1.right, split1.second);
        return t1;
    }

    public static Node remove(Node t, long key) {
        if (t == null) return null;
        if (t.key == key) {
            t = merge(t.left, t.right);
        } else {
            if (key < t.key) {
                t.left = remove(t.left, key);
            } else {
                t.right = remove(t.right, key);
            }
        }
        update(t);
        return t;
    }

    public static Node find(Node t, long key) {
        if (t == null) return null;
        if (t.key == key) return t;
        if (t.key < key) {
            return find(t.right, key);
        } else {
            return find(t.left, key);
        }
    }

    /**
     * Вырезает отрезок от i до j и вставляет в позицию k нового массива
     * (при k = 0 вставляет вначало)
     *
     * @param t Корень дерева
     * @param i Начало отрезка (включительно)
     * @param j Конец отрезка (включительно)
     * @param k Позиция вставки
     * @return Новое дерево
     */
    public static Node replaceSegment(Node t, int i, int j, int k) {
        //Вырезаем отрезок и склеиваем
        Pair<Node, Node> split1 = split(t, i, 0);
        Pair<Node, Node> split2 = split(split1.second, j - i + 1, 0);
        Node merge1 = merge(split1.first, split2.second);
        Node removedSegment = split2.first;

//      out.println("Array without removed:" + getString(merge1));
//      out.println("Removed Segment:" + getString(removedSegment));

        //разделяем и вставляем
        Pair<Node, Node> split3 = split(merge1, k, 0);
        Node merge2 = merge(split3.first, removedSegment);
        Node merge3 = merge(merge2, split3.second);

        return merge3;
    }

    public static long getSum(Node t, long l, long r) {
        Pair<Node, Node> p1 = split(t, l);
        Pair<Node, Node> p2 = split(p1.second, r + 1);
        long ans = p2.first == null ? 0 : p2.first.sum;
        t = merge(p1.first, p2.first);
        t = merge(t, p2.second);
        return ans;
    }

    public static Node apply(Node t, char c, int v) {
        if (t == null) return t;
        Node res;
        if (c == '<') {
            Pair<Node, Node> split1 = split(t, v);
//            out.println(getKeysString(split1.first));
//            out.println(getKeysString(split1.second));
            if (split1.first != null) split1.first.f *= -1;
            res = unite(split1.first, split1.second);
        } else {
            v++;
            Pair<Node, Node> split1 = split(t, v);
//            out.println(getKeysString(split1.first));
//            out.println(getKeysString(split1.second));
            if (split1.second != null) split1.second.f *= -1;
            res = unite(split1.first, split1.second);
        }
        return res;
    }

    public static StringBuilder getKeysString(Node t) {
        if (t == null) return new StringBuilder();
        StringBuilder ans = new StringBuilder();
        if (t.left != null) ans.append(getKeysString(t.left));
        ans.append(t.key);
        ans.append(" ");
        if (t.right != null) ans.append(getKeysString(t.right));
        return ans;
    }

    public static class Pair<F, S> {
        public F first;
        public S second;

        Pair(F first, S second) {
            this.first = first;
            this.second = second;
        }

//        @Override
//        public int compareTo(Object o) {
//            return first.compareTo(((Pair)o).first);
//        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof Main.Pair)) {
                return false;
            }
            Main.Pair<?, ?> p = (Main.Pair<?, ?>) o;
            return Objects.equals(p.first, first) && Objects.equals(p.second, second);
        }

        @Override
        public int hashCode() {
            return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
        }
    }
}