#include "bits/stdc++.h"

#pragma warning(disable:4996)
using namespace std;
using ld = long double;

struct Edge {
	int src;
	int dst;
	int id;
};

// res: bridges
// v: current node
// from_id: parent edge
int bri_dfs(vector<int>&oks,vector<vector<Edge>>&g, vector<int>& res, int now_node, int& count, int from_id, vector<int>& low, vector<int>& pre) {
	if(oks[now_node])return 0;
	else oks[now_node]=true;
	pre[now_node] = count++;
	low[now_node] = pre[now_node];
	for (auto it = g[now_node].begin(); it != g[now_node].end(); it++) {
		int to = it->dst;
		if (pre[to] == -1) {
			// destination has not been visited
			// visit destination and update low[now_node]
			low[now_node] = min(low[now_node], bri_dfs(oks,g, res, to, count, it->id, low, pre));
			if (low[to] == pre[to]) {
				// edge is not contained in a closed path -> bridge
				//res.push_back(it->id);
				res[it->id] = true;
			}
		}
		else {
			if (from_id == it->id) {
				// ignore a path to parent
				continue;
			}
			low[now_node] = min(low[now_node], low[to]);
		}
	}
	return low[now_node];
}

// Calculate bridges in a undirected graph.
// Assume graph is connected and has no parallel edges or self-loops.
// g: adjacency list
// V: number of nodes
void  bridges(vector<vector<Edge>>&g, int V, vector<int>&res) {

	if (V > 0) {
		// assume at least the first vertex exists
		vector<int> low(V, -1);  // lowest reacheable index
		vector<int> pre(V, -1);  // pre-order index
		int count = 0;  // pre-order index counter
		vector<int>oks(V);
		for(int k=0;k<V;++k)bri_dfs(oks,g, res, k, count, -1, low, pre);  // start dfs from vertex 0
	}
}

struct UnionFind {
	vector<int> data;
	UnionFind(int size) : data(size, -1) { }
	bool unionSet(int x, int y) {
		x = root(x); y = root(y);
		if (x != y) {
			if (data[y] < data[x]) swap(x, y);
			data[x] += data[y]; data[y] = x;
		}
		return x != y;
	}
	int root(int x) {
		return data[x] < 0 ? x : data[x] = root(data[x]);
	}
	int size(int x) {
		return -data[root(x)];
	}
};

pair<int, int>dfs(const int now, const int from,
	const vector<vector<int>>&edges,
	vector<int>&comes,
	const vector<int>&nums) {
	comes[now]=true;

	pair<int,int>sum=make_pair(nums[now],0);
	for (auto e : edges[now]) {
		if(e==from)continue;
		else {
			auto p=dfs(e,now,edges,comes,nums);
			sum.first+=p.second;
			sum.second+=max(p.second,p.first);
		}
	}
	return sum;
}

int main() {
	int N,M;cin>>N>>M;
	vector<vector<Edge>>g(N);
	for (int i = 0; i < M; ++i) {
		int a,b;cin>>a>>b;
		g[a].push_back(Edge{ a,b,i });
		g[b].push_back(Edge{ b,a,i });
	}
	vector<int>res(M);
	bridges(g,N,res);
	vector<int>nums(N);
	vector<vector<int>>nextedges(N);
	{
		UnionFind uf(N);
		for (int i = 0; i < N; ++i) {
			for (auto e : g[i]) {
				if (!res[e.id]) {
					uf.unionSet(e.src,e.dst);
				}
			}
		}
		for (int i = 0; i < N; ++i) {
			nums[uf.root(i)]++;
			for (auto e : g[i]) {
				if (res[e.id]) {
					int f=uf.root(e.src);
					int t=uf.root(e.dst);
					nextedges[f].push_back(t);
				}
			}
		}
	}
	vector<int>comes(N);
	int answer=0;
	for (int i = 0; i < N; ++i) {
		if (!comes[i]) {
			auto p=dfs(i,-1,nextedges,comes,nums);
			answer+=max(p.first,p.second);
		}
	}
	cout<<answer<<endl;
	return 0;
}


Measured memory_bytes: 15952