
{-# LANGUAGE BangPatterns          #-}

import           Control.Monad.Primitive
import           Data.List (unfoldr)
import           Control.Monad.ST
import           Control.Monad.State.Strict
import qualified Data.ByteString.Char8       as C
import qualified Data.ByteString.Unsafe      as B
import           Data.Char
import qualified Data.Vector.Unboxed         as U
import qualified Data.Vector.Unboxed.Mutable as UM

find_ :: PrimMonad m => UM.MVector (PrimState m) Int -> Int -> m Int
find_ v x = go x return
    where
        go !x k = do {
            px <- UM.unsafeRead v x;
            if px < 0
                then 
                    k x
                else
                    go px $ \ppx -> do{
                        UM.unsafeWrite v x ppx;
                        k ppx;
                    }
        }

main :: IO ()
main = do
    [n, m] <- unfoldr (C.readInt . C.dropWhile isSpace) <$> C.getLine
    es <- U.unfoldrN n parseInt3 <$> C.getContents
    print es
    print $ U.foldl' (\s v -> if (-1) == v then 1 + s else s) 0 $ runST $ do {
        arr <- UM.replicate n (-1::Int);
        U.forM_ es (\(x, y, z) -> do { 
            x_ <- find_ arr (x-1);
            y_ <- find_ arr (y-1);
            if x_ == y_ 
                then return ()
                else
                    UM.unsafeWrite arr x_ y_; 
        });
        U.freeze arr;
    }

parseInt3 :: C.ByteString -> Maybe ((Int, Int, Int), C.ByteString)
parseInt3 = runStateT $
    (,,) <$> StateT (C.readInt . C.dropWhile isSpace)
        <*> StateT (C.readInt . B.unsafeTail)
        <*> StateT (C.readInt . B.unsafeTail)
