Problem:
Score : 400 points

Problem Statement

There are N persons called Person 1 through Person N.

You are given M facts that "Person A_i and Person B_i are friends." The same fact may be given multiple times.

If X and Y are friends, and Y and Z are friends, then X and Z are also friends. There is no friendship that cannot be derived from the M given facts.

Takahashi the evil wants to divide the N persons into some number of groups so that every person has no friend in his/her group.

At least how many groups does he need to make?

Constraints

2 \leq N \leq 2\times 10^5

0 \leq M \leq 2\times 10^5

1\leq A_i,B_i\leq N

A_i \neq B_i

Input

Input is given from Standard Input in the following format:

N M
A_1 B_1
\vdots
A_M B_M

Output

Print the answer.

Sample Input 1

5 3
1 2
3 4
5 1

Sample Output 1

3

Dividing them into three groups such as \{1,3\}, \{2,4\}, and \{5\} achieves the goal.

Sample Input 2

4 10
1 2
2 1
1 2
2 1
1 2
1 3
1 4
2 3
2 4
3 4

Sample Output 2

4

Sample Input 3

10 4
3 1
4 1
5 9
2 6

Sample Output 3

3

Solution in Rust (1.42.0) (judge verdict: Accepted)

// SNIPPET vec_union_find_sets
pub struct VecUnionFindSets {
    set_count: usize,  items: Vec<VecUnionFindNode>
}
#[derive(Clone)]
struct VecUnionFindNode {
    parent: std::cell::Cell<usize>,
    len: usize
}
impl VecUnionFindNode {
    fn new(item: usize) -> VecUnionFindNode {
        VecUnionFindNode {
            parent: std::cell::Cell::new(item),
            len: 1
        }
    }
}
impl VecUnionFindSets {
    pub fn new() -> VecUnionFindSets {
        VecUnionFindSets {
            set_count: 0,
            items: Vec::new()
        }
    }
    pub fn with_items(items_count: usize) -> VecUnionFindSets {
        let mut sets = VecUnionFindSets::new();
        sets.add_items(items_count);
        sets
    }
    fn error_msg(items: &[usize]) -> String {
        assert!(items.len() == 1 || items.len() == 2);
        if items.len() == 1 {
            format!("no set contains {}", items[0])
        } else {
            format!("no set contains {} and no set contains {}", items[0], items[1])
        }
    }
    pub fn add_items(&mut self, count: usize) {
        self.set_count += count;
        let new_items = (self.items_len()..self.items_len()+count)
            .map(|i| VecUnionFindNode::new(i));
        self.items.extend(new_items);
    }
    pub fn items_len(&self) -> usize {
        self.items.len()
    }
    fn find(&self, item: usize) -> Option<usize> {
        if item >= self.items_len() {
            return None;
        }
        fn go(sets: &VecUnionFindSets, item: usize) -> usize {
            let node = &sets.items[item];
            if node.parent.get() == item {
                return item;
            }
            let root = go(sets, node.parent.get());
            sets.items[root].parent.set(root);
            root
        }
        Some(go(self, item))
    }
    pub fn count(&self) -> usize {
        self.set_count
    }
    pub fn len_of(&self, item: usize) -> Result<usize, String> {
        self.find(item).map(|root| self.items[root].len).ok_or_else(|| {
            VecUnionFindSets::error_msg(&[item])
        })
    }
    pub fn set_eq(&self, item1: usize, item2: usize) -> Result<bool, String> {
        match (self.find(item1), self.find(item2)) {
            (Some(root1), Some(root2)) => Ok(root1 == root2),
            (Some(_), None) => Err(VecUnionFindSets::error_msg(&[item2])),
            (None, Some(_)) => Err(VecUnionFindSets::error_msg(&[item1])),
            (None, None) => Err(VecUnionFindSets::error_msg(&[item1, item2])),
        }
    }
    pub fn unite(&mut self, item1: usize, item2: usize) -> Result<bool, String> {
        match (self.find(item1), self.find(item2)) {
            (Some(root1), Some(root2)) => {
                if root1 == root2 {
                    return Ok(false);
                }
                self.set_count -= 1;
                let (new_root, new_child) = if self.items[root1].len < self.items[root2].len {
                    (root2, root1)
                } else {
                    (root1, root2)
                };
                let new_len = self.items[root1].len + self.items[root2].len;
                self.items[new_root] = VecUnionFindNode {
                    parent: std::cell::Cell::new(new_root),
                    len: new_len
                };
                self.items[new_child] = VecUnionFindNode {
                    parent: std::cell::Cell::new(new_root),
                    len: 0
                };
                Ok(true)
            },
            (Some(_), None) => Err(VecUnionFindSets::error_msg(&[item2])),
            (None, Some(_)) => Err(VecUnionFindSets::error_msg(&[item1])),
            (None, None) => Err(VecUnionFindSets::error_msg(&[item1, item2]))
        }
    }
    pub fn iter_cloned(&self) -> impl Iterator<Item=Vec<usize>> {
        let mut sets = vec![Vec::new(); self.items_len()];
        for i in 0..self.items_len() {
            let repr = self.find(i).unwrap();
            sets[repr].push(i);
        }
        sets.into_iter().filter(|v| !v.is_empty())
    }
}
impl std::fmt::Debug for VecUnionFindSets {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{{")?;
        let mut first = true;
        for set in self.iter_cloned() {
            if !first {
                write!(f, ", ")?;
            }
            write!(f, "{:?}", set)?;
            first = false;
        }
        write!(f, "}}")
    }
}
/*
impl<T: Eq + std::hash::Hash + std::fmt::Debug> IntoIterator for HashUnionFindSets<T> {
    type Item = HashSet<T>;
    type IntoIter = std::collections::hash_map::Values<>;
    fn into_iter(self) -> Self::IntoIter {
    }
}
*/

// SNIPPET read
pub trait Readable {
    type Output;
    const WORD_COUNT: usize;
    fn read_words(words: &[&str]) -> Result<Self::Output, String>;
}
#[macro_export]
macro_rules! readable {
    ( $t:ty, $words_count:expr, |$words:ident| $read_words:expr ) => {
        impl Readable for $t {
            type Output = $t;
            const WORD_COUNT: usize = $words_count;
            fn read_words($words: &[&str]) -> Result<$t, String> {
                Ok($read_words)
            }
        }
    };
}
readable!((), 1, |_ss| ());
readable!(String, 1, |ss| ss[0].to_string());
impl Readable for char {
    type Output = char;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<char, String> {
        let chars: Vec<char> = words[0].chars().collect();
        if chars.len() == 1 {
            Ok(chars[0])
        } else {
            Err(format!("cannot parse `{}` as a char", words[0]))
        }
    }
}
pub struct Chars();
impl Readable for Chars {
    type Output = Vec<char>;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Vec<char>, String> {
        Ok(words[0].chars().collect())
    }
}
pub struct Bytes();
impl Readable for Bytes {
    type Output = Vec<u8>;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Vec<u8>, String> {
        Ok(words[0].bytes().collect())
    }
}
impl Readable for i8 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i8, String> {
        use std::str::FromStr;
        i8::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i8", words[0])
        })
    }
}
impl Readable for u8 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u8, String> {
        use std::str::FromStr;
        u8::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u8", words[0])
        })
    }
}
impl Readable for i16 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i16, String> {
        use std::str::FromStr;
        i16::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i16", words[0])
        })
    }
}
impl Readable for u16 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u16, String> {
        use std::str::FromStr;
        u16::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u16", words[0])
        })
    }
}
impl Readable for i32 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i32, String> {
        use std::str::FromStr;
        i32::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i32", words[0])
        })
    }
}
impl Readable for u32 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u32, String> {
        use std::str::FromStr;
        u32::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u32", words[0])
        })
    }
}
impl Readable for i64 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i64, String> {
        use std::str::FromStr;
        i64::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i64", words[0])
        })
    }
}
impl Readable for u64 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u64, String> {
        use std::str::FromStr;
        u64::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u64", words[0])
        })
    }
}
impl Readable for i128 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i128, String> {
        use std::str::FromStr;
        i128::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i64", words[0])
        })
    }
}
impl Readable for u128 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u128, String> {
        use std::str::FromStr;
        u128::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u64", words[0])
        })
    }
}
impl Readable for isize {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<isize, String> {
        use std::str::FromStr;
        <isize>::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as isize", words[0])
        })
    }
}
impl Readable for usize {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<usize, String> {
        use std::str::FromStr;
        <usize>::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as usize", words[0])
        })
    }
}
impl Readable for f32 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<f32, String> {
        use std::str::FromStr;
        f32::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as f32", words[0])
        })
    }
}
impl Readable for f64 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<f64, String> {
        use std::str::FromStr;
        f64::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as f64", words[0])
        })
    }
}
#[allow(non_camel_case_types)]
pub struct u8_;
impl Readable for u8_ {
    type Output = u8;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u8::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct u16_;
impl Readable for u16_ {
    type Output = u16;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u16::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct u32_;
impl Readable for u32_ {
    type Output = u32;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u32::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct u64_;
impl Readable for u64_ {
    type Output = u64;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u64::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct usize_;
impl Readable for usize_ {
    type Output = usize;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        <usize>::read_words(words).map(|n| n-1)
    }
}
impl<T1: Readable, T2: Readable> Readable for (T1, T2) {
    type Output = (T1::Output, T2::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let val2 = T2::read_words(&words[start..])?;
        Ok((val1, val2))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable> Readable for (T1, T2, T3) {
    type Output = (T1::Output, T2::Output, T3::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let val3 = T3::read_words(&words[start..])?;
        Ok((val1, val2, val3))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable> Readable for (T1, T2, T3, T4) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT + T4::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let count3 = T3::WORD_COUNT;
        let val3 = T3::read_words(&words[start .. start+count3])?;
        start += count3;
        let val4 = T4::read_words(&words[start..])?;
        Ok((val1, val2, val3, val4))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable, T5: Readable> Readable for (T1, T2, T3, T4, T5) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output, T5::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT + T4::WORD_COUNT + T5::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let count3 = T3::WORD_COUNT;
        let val3 = T3::read_words(&words[start .. start+count3])?;
        start += count3;
        let count4 = T4::WORD_COUNT;
        let val4 = T4::read_words(&words[start .. start+count4])?;
        start += count4;
        let val5 = T5::read_words(&words[start..])?;
        Ok((val1, val2, val3, val4, val5))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable, T5: Readable, T6: Readable> Readable for (T1, T2, T3, T4, T5, T6) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output, T5::Output, T6::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT + T4::WORD_COUNT + T5::WORD_COUNT + T6::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let count3 = T3::WORD_COUNT;
        let val3 = T3::read_words(&words[start .. start+count3])?;
        start += count3;
        let count4 = T4::WORD_COUNT;
        let val4 = T4::read_words(&words[start .. start+count4])?;
        start += count4;
        let count5 = T5::WORD_COUNT;
        let val5 = T5::read_words(&words[start .. start+count5])?;
        start += count5;
        let val6 = T6::read_words(&words[start..])?;
        Ok((val1, val2, val3, val4, val5, val6))
    }
}
impl<T: Readable> Readable for [T; 2] {
    type Output = [T::Output; 2];
    const WORD_COUNT: usize = T::WORD_COUNT * 2;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        Ok([val1, val2])
    }
}
impl<T: Readable> Readable for [T; 3] {
    type Output = [T::Output; 3];
    const WORD_COUNT: usize = T::WORD_COUNT * 3;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        Ok([val1, val2, val3])
    }
}
impl<T: Readable> Readable for [T; 4] {
    type Output = [T::Output; 4];
    const WORD_COUNT: usize = T::WORD_COUNT * 4;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        let val4 = T::read_words(&words[T::WORD_COUNT*3 .. T::WORD_COUNT*4])?;
        Ok([val1, val2, val3, val4])
    }
}
impl<T: Readable> Readable for [T; 5] {
    type Output = [T::Output; 5];
    const WORD_COUNT: usize = T::WORD_COUNT * 5;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        let val4 = T::read_words(&words[T::WORD_COUNT*3 .. T::WORD_COUNT*4])?;
        let val5 = T::read_words(&words[T::WORD_COUNT*4 .. T::WORD_COUNT*5])?;
        Ok([val1, val2, val3, val4, val5])
    }
}
impl<T: Readable> Readable for [T; 6] {
    type Output = [T::Output; 6];
    const WORD_COUNT: usize = T::WORD_COUNT * 6;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        let val4 = T::read_words(&words[T::WORD_COUNT*3 .. T::WORD_COUNT*4])?;
        let val5 = T::read_words(&words[T::WORD_COUNT*4 .. T::WORD_COUNT*5])?;
        let val6 = T::read_words(&words[T::WORD_COUNT*5 .. T::WORD_COUNT*6])?;
        Ok([val1, val2, val3, val4, val5, val6])
    }
}
pub trait ReadableFromLine {
    type Output;
    fn read_line(line: &str) -> Result<Self::Output, String>;
}
fn split_into_words(line: &str) -> Vec<&str> {
    line.trim_end_matches('\n').split_whitespace().collect()
}
impl<T: Readable> ReadableFromLine for T {
    type Output = T::Output;
    fn read_line(line: &str) -> Result<T::Output, String> {
        let words = split_into_words(line);
        if words.len() != T::WORD_COUNT {
            return Err(format!("line `{}` has {} words, expected {}",
                               line, words.len(), T::WORD_COUNT));
        }
        T::read_words(&words)
    }
}
pub fn read_words_into_vec<T: Readable>(words: &[&str], line: &str) -> Result<Vec<T::Output>, String> {
    let n = T::WORD_COUNT;
    assert_eq!(words.len() % n, 0);
    let mut result = Vec::new();
    for chunk in words.chunks(n) {
        match T::read_words(chunk) {
            Ok(v) => result.push(v),
            Err(msg) => {
                let fragment_msg = if n == 1 {
                    format!("word {}", result.len())
                } else {
                    let l = result.len();
                    format!("words {}-{}", n*l + 1, (n+1) * l)
                };
                return Err(format!(
                    "{} of line `{}`: {}", fragment_msg, line, msg
                ));
            }
        }
    }
    Ok(result)
}
pub fn split_into_words_for_collection<T: Readable>(
    line: &str, prefix_words_count: usize
) -> Result<Vec<&str>, String> {
    let n = T::WORD_COUNT;
    let words = split_into_words(line);
    if words.len() < prefix_words_count {
        return Err(
            format!("line `{}` has {} words, expected at least {}",
                    line, words.len(), prefix_words_count)
        );
    }
    if (words.len() - prefix_words_count) % T::WORD_COUNT != 0 {
        return Err(
            format!("line `{}` has {} words, expected {} + {}",
                    line, words.len(), prefix_words_count, n)
        );
    }
    Ok(words)
}
#[macro_export]
macro_rules! readable_collection {
    ($u:ident => $collection_in:ty, $collection_out:ty) => {
        readable_collection!($u: => $collection_in, $collection_out);
    };
    ($u:ident : $( $bound:path ),* => $collection_in:ty, $collection_out:ty) => {
        impl<$u: Readable> ReadableFromLine for $collection_in
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = $collection_out;
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let words = split_into_words_for_collection::<$u>(line, 0)?;
                Ok(read_words_into_vec::<$u>(&words, line)?.into_iter().collect())
            }
        }
        impl<T1: Readable, $u: Readable> ReadableFromLine for (T1, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = T1::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let val1 = T1::read_words(&words[..prefix_len])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, rest.into_iter().collect()))
            }
        }
        impl<T1: Readable, T2: Readable, $u: Readable> ReadableFromLine for (T1, T2, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, T2::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = <(T1, T2)>::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let mut start = 0;
                let count1 = T1::WORD_COUNT;
                let val1 = T1::read_words(&words[start .. start+count1])?;
                start += count1;
                let count2 = T2::WORD_COUNT;
                let val2 = T2::read_words(&words[start .. start+count2])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, val2, rest.into_iter().collect()))
            }
        }
        impl<T1: Readable, T2: Readable, T3: Readable, $u: Readable> ReadableFromLine for (T1, T2, T3, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, T2::Output, T3::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = <(T1, T2, T3)>::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let mut start = 0;
                let count1 = T1::WORD_COUNT;
                let val1 = T1::read_words(&words[start .. start+count1])?;
                start += count1;
                let count2 = T2::WORD_COUNT;
                let val2 = T2::read_words(&words[start .. start+count2])?;
                start += count2;
                let count3 = T3::WORD_COUNT;
                let val3 = T3::read_words(&words[start .. start+count3])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, val2, val3, rest.into_iter().collect()))
            }
        }
        impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable, $u: Readable> ReadableFromLine for (T1, T2, T3, T4, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, T2::Output, T3::Output, T4::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = <(T1, T2, T3, T4)>::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let mut start = 0;
                let count1 = T1::WORD_COUNT;
                let val1 = T1::read_words(&words[start .. start+count1])?;
                start += count1;
                let count2 = T2::WORD_COUNT;
                let val2 = T2::read_words(&words[start .. start+count2])?;
                start += count2;
                let count3 = T3::WORD_COUNT;
                let val3 = T3::read_words(&words[start .. start+count3])?;
                start += count3;
                let count4 = T4::WORD_COUNT;
                let val4 = T4::read_words(&words[start .. start+count4])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, val2, val3, val4, rest.into_iter().collect()))
            }
        }
    };
}
readable_collection!(U => Vec<U>, Vec<U::Output>);
pub fn read<T: ReadableFromLine>() -> T::Output {
    let mut line = String::new();
    std::io::stdin().read_line(&mut line).unwrap();
    T::read_line(&line).unwrap()
}
#[macro_export]
macro_rules! read {
    () => {
        let mut line = String::new();
        std::io::stdin().read_line(&mut line).unwrap();
    };
    ( $pat:pat = $t:ty $(,)* ) => {
        let $pat = read::<$t>();
    };
    ( ! $(,)* ) => {
        let _ = read::<()>();
    };
    ( $pat:pat = $t:ty, $( $rest:tt )+ ) => {
        read_inner!($pat = $t; $($rest)+);
    };
    ( !, $( $rest:tt )+ ) => {
        read_inner!(_ = (); $($rest)+);
    };
}
#[macro_export]
#[doc(hidden)]
macro_rules! read_inner {
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; $pat:pat = $t:ty, $( $rest:tt )* ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , $pat = $t ; $($rest)*);
    };
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; !, $( $rest:tt )* ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , _ = () ; $($rest)*);
    };
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; $pat:pat = $t:ty ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , $pat = $t ;);
    };
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; ! ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , _ = () ;);
    };
    ( $( $pat:pat = $t:ty ),+ ; ) => {
        let ($($pat),+) = read::<($($t),+)>();
    };
}
pub trait ReadableFromChunk {
    type Output;
    fn lines_count() -> usize;
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String>;
}
impl<T1: ReadableFromLine, T2: ReadableFromLine> ReadableFromChunk for (T1, T2) {
    type Output = (T1::Output, T2::Output);
    fn lines_count() -> usize { 2 }
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String> {
        let out1 = T1::read_line(&lines[0])?;
        let out2 = T2::read_line(&lines[1])?;
        Ok((out1, out2))
    }
}
impl<T1: ReadableFromLine, T2: ReadableFromLine, T3: ReadableFromLine> ReadableFromChunk for (T1, T2, T3) {
    type Output = (T1::Output, T2::Output, T3::Output);
    fn lines_count() -> usize { 3 }
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String> {
        let out1 = T1::read_line(&lines[0])?;
        let out2 = T2::read_line(&lines[1])?;
        let out3 = T3::read_line(&lines[2])?;
        Ok((out1, out2, out3))
    }
}
impl<T1: ReadableFromLine, T2: ReadableFromLine, T3: ReadableFromLine, T4: ReadableFromLine> ReadableFromChunk for (T1, T2, T3, T4) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output);
    fn lines_count() -> usize { 4 }
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String> {
        let out1 = T1::read_line(&lines[0])?;
        let out2 = T2::read_line(&lines[1])?;
        let out3 = T3::read_line(&lines[2])?;
        let out4 = T4::read_line(&lines[3])?;
        Ok((out1, out2, out3, out4))
    }
}
pub fn read_chunk<T: ReadableFromChunk>() -> T::Output {
    let stdin = std::io::stdin();
    let mut handle = stdin.lock();
    read_chunk_from_handle::<T>(&mut handle).unwrap()
}
fn read_chunk_from_handle<T: ReadableFromChunk>(handle: &mut std::io::StdinLock) -> Option<T::Output> {
    use std::io::BufRead;
    let mut lines = vec![String::new(); T::lines_count()];
    let mut first = true;
    for line in &mut lines {
        if handle.read_line(line).unwrap() == 0 && first {
            return None;
        }
        first = false;
    }
    Some(T::read_chunk(&lines).unwrap())
}
#[macro_export]
macro_rules! read_chunk {
    ( $( $pat:pat = $t:ty ),+ ) => {
        let ($($pat),+) = read_chunk::<($($t),+)>();
    };
}
static mut STDIN: Option<std::io::Stdin> = None;
pub struct ReadLines<T: ReadableFromLine> {
    lock: std::io::StdinLock<'static>,
    phantom: std::marker::PhantomData<T>
}
impl<T: ReadableFromLine> Iterator for ReadLines<T> {
    type Item = T::Output;
    fn next(&mut self) -> Option<T::Output> {
        use std::io::BufRead;
        let mut line = String::new();
        if self.lock.read_line(&mut line).unwrap() > 0 {
            Some(T::read_line(&line).unwrap())
        } else {
            None
        }
    }
}
pub fn read_lines<T: ReadableFromLine>() -> ReadLines<T> {
    unsafe {
        if STDIN.is_none() {
            STDIN = Some(std::io::stdin());
        }
    }
    ReadLines {
        lock: unsafe { STDIN.as_ref().unwrap().lock() },
        phantom: std::marker::PhantomData::<T>
    }
}
pub struct ReadChunks<T: ReadableFromChunk> {
    lock: std::io::StdinLock<'static>,
    phantom: std::marker::PhantomData<T>
}
impl<T: ReadableFromChunk> Iterator for ReadChunks<T> {
    type Item = T::Output;
    fn next(&mut self) -> Option<T::Output> {
        read_chunk_from_handle::<T>(&mut self.lock)
    }
}
pub fn read_chunks<T: ReadableFromChunk>() -> ReadChunks<T> {
    unsafe {
        if STDIN.is_none() {
            STDIN = Some(std::io::stdin());
        }
    }
    ReadChunks {
        lock: unsafe { STDIN.as_ref().unwrap().lock() },
        phantom: std::marker::PhantomData::<T>
    }
}
pub trait Words {
    fn read<T: Readable>(&self) -> T::Output;
}
impl<'a> Words for [&'a str] {
    fn read<T: Readable>(&self) -> T::Output {
        T::read_words(self).unwrap()
    }
}
impl<'a> Words for &'a str {
    fn read<T: Readable>(&self) -> T::Output {
        T::read_words(&[self]).unwrap()
    }
}

// SNIPPET utils
#[macro_export]
macro_rules! echo {
    () => {
        println!()
    };
    ($e: expr $(,)?) => {
        println!("{}", $e)
    };
    ($e: expr, $($es: expr),+ $(,)?) => {
        {
            use std::io::Write;
            let stdout = std::io::stdout();
            let mut handle = stdout.lock();
            write!(handle, "{}", $e).unwrap();
            $(
                write!(handle, " {}", $es).unwrap();
            )+
            writeln!(handle).unwrap();
        }
    };
}
pub fn yn(result: bool) {
    if result {
        println!("Yes");
    } else {
        println!("No");
    }
}
#[allow(non_snake_case)]
pub fn YN(result: bool) {
    if result {
        println!("YES");
    } else {
        println!("NO");
    }
}
pub fn exit(msg: impl std::fmt::Display) -> ! {
    println!("{}", msg);
    std::process::exit(0)
}
#[macro_export]
#[cfg(local)]
macro_rules! dbg {
    () => {
        {
            use std::io::{self, Write};
            writeln!(io::stderr(), "{}: dbg", line!()).unwrap();
        }
    };
    ($e: expr) => {
        {
            use std::io::{self, Write};
            let result = $e;
            writeln!(io::stderr(), "{}: {} = {:?}",
                     line!(), stringify!($e), result)
                .unwrap();
            result
        }
    }
}
#[macro_export]
#[cfg(not(local))]
macro_rules! dbg {
    () => {};
    ($e: expr) => {
        { $e }
    }
}

// END SNIPPETS
// Here is the documentation: https://yoshrc.github.io/rust-atcoder-snippets/atcoder_snippets/index.html

fn main() {
    read!(n = usize, _m = usize);
    let mut sets = VecUnionFindSets::with_items(n);
    for (a, b) in read_lines::<(usize_, usize_)>() {
        sets.unite(a, b).unwrap();
    }
    echo!(sets.iter_cloned().map(|s| s.len()).max().unwrap());
}