import Foundation

// MARK: READ INPUTS

func readInt() -> Int {
    Int(readLine()!)!
}

func readInts() -> [Int] {
    readLine()!.split(separator: " ").map { Int($0)! }
}

// MARK: DP

struct DP<Argument: Hashable, Element> {
    var dictionary = [Argument: Element]()
    
    var expression: (Argument, (Argument) -> Element) -> Element
    
    init(expression: @escaping (_ argument: Argument, _ note: (Argument) -> Element) -> Element) {
        self.expression = expression
    }
    
    subscript(_ argument: Argument) -> Element {
        mutating _read {
            if let note = dictionary[argument] {
                yield note
            } else {
                let result = expression(argument) { self[$0] }
                dictionary[argument] = result
                yield result
            }
        }
    }
}

// MARK: SOME EXTENSIONS

extension Dictionary {
    init(_ initialize: (inout Self) -> ()) {
        var dictionary = Self()
        initialize(&dictionary)
        self = dictionary
    }
}

extension SignedNumeric where Self: Comparable {
    var abs: Self {
        return self < .zero ? -self : self
    }
}

extension String {
    init(_ text: String, code: Int) {
        self = "\u{1b}[\(code)m" + text + "\u{1b}[0m"
    }
}

extension Collection {
    func tuple() -> (Element, Element) {
        var i = makeIterator()
        return (i.next()!, i.next()!)
    }
    func tuple() -> (Element, Element, Element) {
        var i = makeIterator()
        return (i.next()!, i.next()!, i.next()!)
    }
    func tuple() -> (Element, Element, Element, Element) {
        var i = makeIterator()
        return (i.next()!, i.next()!, i.next()!, i.next()!)
    }
    
    func safe(at index: Index) -> Element? {
        self.indices.contains(index) ? self[index] : nil
    }
}

// MARK: MOD

@propertyWrapper
struct Mod {
    
    let base: Int
    private var store: Int
    
    var value: Int {
        get { return store }
        set {
            store = newValue % base
            if store < 0 { store += base }
        }
    }
    
    static var defaultBaseNumber = 1_000_000_007
    
    init(_ value: Int, base: Int = defaultBaseNumber) {
        self.base = base
        self.store = value % base
        if store < 0 { self.store += base }
    }
    
    private init(unchecked value: Int, base: Int = defaultBaseNumber) {
        self.store = value
        self.base = base
    }
    
    init(wrappedValue: Int, base: Int = defaultBaseNumber) {
        self = .init(wrappedValue, base: base)
    }
    
    var wrappedValue: Int {
        get { return value }
        set { value = newValue }
    }
    
    var projectedValue: Int { base }
}

extension Mod: ExpressibleByIntegerLiteral {
    init(integerLiteral value: Int) {
        self = .init(value)
    }
}

extension Mod: AdditiveArithmetic {
    
    private static func assert(_ a: Mod, _ b: Mod) {
        Swift.assert(b.base == b.base, "Base should be same number .")
    }
    
    private static func operate(_ f: @escaping (Int, Int) -> Int) -> (Mod, Mod) -> Mod {
        return { lhs, rhs in
            assert(lhs, rhs)
            let num = f(lhs.value, rhs.value)
            return Mod(num, base: lhs.base)
        }
    }
    
    static func + (lhs: Mod, rhs: Mod) -> Mod { operate(+)(lhs, rhs) }
    static func - (lhs: Mod, rhs: Mod) -> Mod { operate(-)(lhs, rhs) }
    static func * (lhs: Mod, rhs: Mod) -> Mod { operate(*)(lhs, rhs) }
    static func *= (lhs: inout Mod, rhs: Mod) { lhs = lhs * rhs }
}

extension Mod: SignedNumeric {
    init?<T>(exactly source: T) where T : BinaryInteger {
        guard let int = Int(exactly: source) else { return nil }
        self = Mod(int)
    }
    
    var magnitude: Int { value.abs }
}

extension Mod: CustomStringConvertible {
    var description: String {
        let digit = String(base).count
        let string = String(format: "%\(digit)d", value)
        return "Mod(\(string), base: \(base))"
    }
}

extension Mod: Equatable {
    static func == (lhs: Mod, rhs: Mod) -> Bool {
        assert(lhs, rhs)
        return lhs.value == rhs.value
    }
}

extension Mod: Hashable {
    func hash(into hasher: inout Hasher) {
        hasher.combine(base)
        hasher.combine(value)
    }
}

extension Mod: Comparable {
    static func < (lhs: Mod, rhs: Mod) -> Bool {
        assert(lhs, rhs)
        return lhs.value < rhs.value
    }
}

extension Mod: Strideable {
    typealias Stride = Int
    
    func distance(to other: Mod) -> Int {
        return (other - self).value
    }
    
    func advanced(by n: Int) -> Mod {
        return self + Mod(n, base: base)
    }
}

// MARK: PAIR and EITHER

struct Pair<T> {
    var x, y: T
    init(_ x: T, _ y: T) {
        self.x = x
        self.y = y
    }
}

extension Pair: Equatable where T: Equatable {}
extension Pair: Hashable where T: Hashable {}
extension Pair: Codable where T: Codable {}

enum Either<T, U> {
    case a(T), b(U)
}

extension Either: Equatable where T: Equatable, U: Equatable {}
extension Either: Hashable where T: Hashable, U: Hashable {}

// MARK: UnionFind

struct UnionFind {
    var par: [Int]
    init(n: Int) {
        self.par = Array(0...n)
    }
    
    mutating func root(_ x: Int) -> Int {
        if par[x] == x { return x }
        par[x] = root(par[x])
        return par[x]
    }
    
    mutating func unite(_ x: Int, _ y: Int) {
        let rx = root(x), ry = root(y)
        if rx == ry { return }
        par[rx] = ry
    }
    
    mutating func same(_ x: Int, _ y: Int) -> Bool {
        let rx = root(x), ry = root(y)
        return rx == ry
    }
    
    mutating func size() -> Int {
        var dic = [Int: Int]()
        for i in par {
            dic[root(i), default: 0] += 1
        }
        return dic.values.max() ?? 0
    }
}

extension UnionFind: CustomStringConvertible {
    var description: String {
        "UnionFind(\(par))"
    }
}

// MARK: -- MAIN

func problem_c(_ an: [Mod]) -> Int {
    var res = Mod(0)
    var sum = an.reduce(0, +)
    for i in an {
        sum -= i
        res += i * sum
    }
    return res.value
}

func heavy_problem_c(_ an: [Mod]) -> Int {
    var res = Mod(0)
    for i in an.indices {
        let under = an[i]
        for j in 0..<i {
            let upper = an[j]
//            print(under.value, "*", upper.value, "=", (under * upper).value)
            res += under * upper
        }
    }
    return res.value
}


func problem_d(n: Int, data: [(Int, Int)]) -> Int {
    var unionFind = UnionFind(n: n)
    for (x, y) in data {
        unionFind.unite(x, y)
    }
    
    return unionFind.size()
}



func main() {
    let (n, m) = readInts().tuple()
    let data: [(Int, Int)] = (0..<m).map { _ in readInts().tuple() }
    print(problem_d(n: n, data: data))
}

func test(_ n: Int = 10) {
    func judge<T: Equatable>(_ answer: T, expected: T) {
        let success = "SUCCESS: \(answer)"
        let failed  = "FAILED:  \(answer) (expected \(expected))"
        print(answer == expected ? success : String(failed, code: 31))
    }
    
    print("\nexample ===================")
    let ex1 = problem_d(n: 5, data: [(1, 2), (3, 4), (5, 1)])
    judge(ex1, expected: 3)
    let ex2 = problem_d(n: 4, data: [(1, 2), (2, 1), (1, 2), (2, 1), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)])
    judge(ex2, expected: 4)
    let ex3 = problem_d(n: 10, data: [(3, 1), (4, 1), (5, 9), (2, 6)])
    judge(ex3, expected: 3)
    
    print("\nborder ====================")
    let bo1 = problem_d(n: 2, data: [])
    judge(bo1, expected: 1)
    let bo2 = problem_d(n: 3, data: [(1, 2), (2, 3)])
    judge(bo2, expected: 3)
    let bo3 = problem_d(n: 4, data: [(1, 2), (3, 4), (1, 3)])
    judge(bo3, expected: 4)
    
    print("\nrandom ====================")
    for _ in 0..<n {
//        let ans = problem_c(an)
//        let hev = heavy_problem_c(an)
//        printResult(ans, expected: hev)
    }
}

main()

Measured memory_bytes: 28612