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 C# (.NET Core 3.1.201) (judge verdict: Runtime Error)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Numerics;

public class MainClass
{
    public static void Main(string[] args)
    {
#if DEBUG
        var inputs = new[] { @"
5 3
1 2
3 4
5 1
        ", @"
4 10
1 2
2 1
1 2
2 1
1 2
1 3
1 4
2 3
2 4
3 4
        ", @"
10 4
3 1
4 1
5 9
2 6
        ", @"

        ", @"

        " };

        var stream = Console.OpenStandardOutput();
        foreach (var str in inputs)
        {
            var text = str.TrimStart();
            if (text.Length == 0) continue;

            //File.ReadAllText(text);
            Console.SetIn(new StringReader(text));
            //using var stream = new FileStream(@$"tester\out_{text}", FileMode.Create);
            Buffered(args, stream);
            Console.WriteLine("----------------------------------");
        }
#else
        Buffered(args, Console.OpenStandardOutput());
#endif
    }

    private static void Buffered(string[] args, Stream stream)
    {
        var sw = new StreamWriter(stream, Console.Out.Encoding);
        sw.AutoFlush = false;
        var beforeOut = Console.Out;
        Console.SetOut(sw);

        Main2(args);

        Console.Out.Flush();
        Console.SetOut(beforeOut);
    }

    private static void Main2(string[] args)
    {
        checked
        {
            var /*NM*/ (N, M) = (Int(), Int());
            var t = new int[N + 1];
            for (int i = 0; i < M; i++)
            {
                var /*AB*/ (A, B) = (Int(), Int());
                if (t[A] == 0 && t[B] == 0)
                {
                    t[A] = -2;
                    t[B] = A;
                }
                else if (t[A] != 0 && t[B] != 0)
                {
                    if (Find(A) != Find(B)) Union(A, B);
                }
                else if (t[A] != 0 && t[B] == 0)
                {
                    var root = Find(A);
                    t[root]--;
                    t[B] = root;
                }
                else
                {
                    var root = Find(B);
                    t[root]--;
                    t[A] = root;
                }
            }

            Console.WriteLine(-t.Min());

            int Find(int idx)
            {
                var list = new List<int>();
                while (t[idx] >= 0)
                {
                    if (t[idx] == 0) throw new Exception();
                    list.Add(idx);
                    idx = t[idx];
                }

                foreach (var branch in list)
                {
                    t[branch] = idx;
                }

                return idx;
            }

            void Union(int to, int from)
            {
                t[to] += t[from];
                t[from] = to;
            }
        }
    }

    #region "入力"

    private static string NextInput = "";

    private static int Int() => int.Parse(Str());
    private static long Long() => long.Parse(Str());
    private static float Float() => float.Parse(Str());
    private static double Double() => double.Parse(Str());
    private static string Str()
    {
        var s = NextInput;
        if (s == "") s = Console.ReadLine().Trim();

        var idx = s.IndexOf(' ');
        if (idx >= 0)
        {
            NextInput = s.Substring(idx + 1).TrimStart();
            return s.Substring(0, idx);
        }
        NextInput = "";
        return s;
    }

    #endregion

}

public static class ComparerPlus
{
    ///<summary>セレクタ関数から昇順のComparerを生成する。</summary>
    public static Comparer<T> By<T, U>(Func<T, U> selector) where U : IComparable
    {
        return Comparer<T>.Create((a, b) => selector(a).CompareTo(selector(b)));
    }

    ///<summary>セレクタ関数から降順のComparerを生成する。</summary>
    public static Comparer<T> ByDecending<T, U>(Func<T, U> selector) where U : IComparable
    {
        return Comparer<T>.Create((a, b) => selector(b).CompareTo(selector(a)));
    }
}

public static class EnumerablePlus
{
    ///<summary>出力文字列に変換。</summary>
    public static string MkString<T>(this IEnumerable<T> list, string sep = null)
    {
        if (sep == null) sep = " ";

        return list.Aggregate(
            new System.Text.StringBuilder(),
            (a, b) => ((a.Length > 0) ? a.Append(sep) : a).Append(b)
            ).ToString();
    }

    ///<summary>添字付きfor。</summary>
    public static void ForEachWithIdx<T>(this IEnumerable<T> list, Action<T, long> a)
    {
        long i = 0;
        foreach (var item in list)
        {
            a(item, i++);
        }
    }

    ///<summary>添字なしfor。</summary>
    public static void ForEach<T>(this IEnumerable<T> list, Action<T> a)
    {
        foreach (var item in list)
        {
            a(item);
        }
    }

    ///<summary>指定回数デリゲートを呼び出す。</summary>
    public static IEnumerable<T> Repeat<T>(long n, Func<T> f)
    {
        for (long i = 0; i < n; i++)
        {
            yield return f();
        }
    }

    ///<summary>条件付きMin。</summary>
    public static T MinBy<T, U>(this IEnumerable<T> xs, Func<T, U> key) where U : IComparable<U>
    {
        return xs.Aggregate((a, b) => key(a).CompareTo(key(b)) <= 0 ? a : b);
    }

    ///<summary>条件付きMax。</summary>
    public static T MaxBy<T, U>(this IEnumerable<T> xs, Func<T, U> key) where U : IComparable<U>
    {
        return xs.Aggregate((a, b) => key(a).CompareTo(key(b)) >= 0 ? a : b);
    }

    ///<summary>条件付きMax。複数の値を返す。</summary>
    public static List<T> MaxByList<T, U>(this IEnumerable<T> xs, Func<T, U> key) where U : IComparable<U>
    {
        return xs.Aggregate(
            new List<T>(),
            (list, b) =>
            {
                if (list.Count == 0) { list.Add(b); return list; }

                var aa = key(list[0]);
                var bb = key(b);

                if (aa.CompareTo(bb) < 0) list.Clear();

                if (aa.CompareTo(bb) <= 0) { list.Add(b); return list; }

                return list;
            });
    }

    ///<summary>複数の値を返す。</summary>
    public static List<T> MaxList<T>(this IEnumerable<T> xs) where T : IComparable<T>
    {
        return xs.MaxByList(o => o);
    }
}

public static class MathPlus
{
    ///<summary>n(>=0)をradix進数に変換する。桁の小さい順に返す。</summary>
    public static List<long> NRadix(long n, long radix)
    {
        if (n < 0) throw new ArgumentOutOfRangeException(nameof(n));
        if (radix < 2) throw new ArgumentOutOfRangeException(nameof(n));

        var res = new List<long>();
        if (n == 0L)
        {
            res.Add(0);
            return res;
        }

        while (n > 0L)
        {
            res.Add(n % radix);
            n /= radix;
        }

        return res;
    }

    ///<summary>べき乗。高速な実装である。</summary>
    public static long Power(long x, long n)
    {
        if (n == 0) return 1;
        if (n % 2 == 0) return Power(x * x, n / 2);
        return x * Power(x, n - 1);
    }

    ///<summary>約数。順番は不定。</summary>
    public static List<long> Divisor(long n)
    {
        var list = Prime(n).
            GroupBy(id => id).
            Select(pair => new { prime = pair.Key, power = pair.Count() }).
            ToList();

        var result = list.Aggregate(new List<long>(), (left, pair) =>
        {
            var list2 = Enumerable.
                Range(0, pair.power + 1).
                Aggregate(new List<long>(), (res, l) =>
                {
                    var next = res.Count > 0 ? res.Last() * pair.prime : 1L;
                    res.Add(next);
                    return res;
                });

            if (left.Count == 0) return list2.ToList();

            return list2.
                SelectMany(l => left.Select(m => l * m)).
                ToList();
        });

        return result;
    }

    ///<summary>順列の数。</summary>
    public static long Permutation(long n, long r)
        => Factorial(n, r);

    ///<summary>組合せの数。</summary>
    public static long Combination(long n, long r)
    {
        if (r == 0) return 1;

        return Factorial(n, r) / Factorial(r);
    }

    ///<summary>階乗。カウントも指定可能。</summary>
    public static long Factorial(long a, long count = long.MaxValue)
    {
        long res = 1;

        for (long i = 0; a > 0 && i < count; i++)
        {
            checked { res *= a; }
            --a;
        }

        return res;
    }

    ///<summary>素因数分解。昇順。</summary>
    public static List<long> Prime(long a)
    {
        var res = new List<long>();

        var a2 = Math.Ceiling(Math.Sqrt(a));
        for (long b = 2; b <= a2; ++b)
        {
            while (a % b == 0)
            {
                res.Add(b);
                a = a / b;
                a2 = Math.Ceiling(Math.Sqrt(a));
            }
        }

        if (a > 1) res.Add(a);
        return res;
    }

    ///<summary>最小公倍数。</summary>
    public static long Lcm(IEnumerable<long> list)
    {
        return list.Aggregate((a, b) => Lcm(a, b));
    }

    ///<summary>最小公倍数。</summary>
    public static long Lcm(long a, long b)
    {
        return checked(a * b) / Gcd(a, b);
    }

    ///<summary>最大公約数。</summary>
    public static long Gcd(IEnumerable<long> list)
    {
        return list.Aggregate((a, b) => Gcd(a, b));
    }

    ///<summary>最大公約数。</summary>
    public static long Gcd(long a, long b)
    {
        if (a < b)
            Gcd(b, a);

        while (b != 0)
        {
            var rem = a % b;
            a = b;
            b = rem;
        }

        return a;
    }

    ///<summary>整数の切り上げ。</summary>
    public static long Ceiling(long a, long b) => (a + (b - 1)) / b;

    ///<summary>区間内の合計。beginも１から？end含む。</summary>
    public static ulong Sum(ulong begin, ulong end)
    {
        var up = Sum(end);
        var low = Sum(begin - 1);
        return up - low;
    }

    ///<summary>1からの合計。</summary>
    public static ulong Sum(ulong end) => checked((end + 1) * end / 2);
}

public static class Util
{
    ///<summary>リスト内の値を移動する。</summary>
    public static void Move<T>(this IList<T> list, int from, int to)
    {
        var item = list[from];

        for (var idx = from; idx > to; --idx)
        {
            list[idx] = list[idx - 1];
        }

        list[to] = item;
    }

    ///<summary>最後の値か、デフォルト値を取得。</summary>
    public static T LastOrValue<T>(this IList<T> list, T value)
    {
        return list.Count > 0 ? list.Last() : value;
    }

    ///<summary>辞書をMultiSetとして使うときのAdd。</summary>
    public static void AddMulti<T>(this IDictionary<T, int> xs, T key)
    {
        if (xs.ContainsKey(key))
            xs[key]++;
        else
            xs.Add(key, 1);
    }

    ///<summary>値を交換する。</summary>
    public static void Swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    public static void Assert(bool cond)
    {
        if (!cond) throw new Exception();
    }

    /// <summary>
    /// データの範囲に対する二分探索を行う。
    /// </summary>
    /// <param name="min">最小値。</param>
    /// <param name="max">最大値。</param>
    /// <param name="Pred">テスト値と目標値を比較する。テスト値が目標値以下/未満ならtrue。それ以外はfalse。</param>
    /// <param name="CalcCenter"><paramref name="min">と<paramref name="max">の中間値を求める処理。</param>
    /// <typeparam name="T">二分探索するデータ型。</typeparam>
    /// <returns><paramref name="Pred">の戻り値が変わる境界の値を返す。</returns>
    public static (T, T) BinarySearch<T>(T min, T max, Predicate<T> Pred, Func<T, T, T> CalcCenter)
        where T : IComparable<T>, IEquatable<T>
    {
        while (min.CompareTo(max) <= 0)
        {
            var center = CalcCenter(min, max);
            if (center.Equals(min) || center.Equals(max)) break;

            if (Pred(center))
                min = center;
            else
                max = center;
        }

        return (min, max);
    }

    public static (int, int) BinarySearch(int min, int max, Predicate<int> Pred)
    {
        return BinarySearch<int>(min, max, Pred, (a, b) => (a + b) / 2);
    }
}