#pragma region template
// clang-format off
#if defined ONLINE_JUDGE
  #pragma GCC target("avx2")
  #pragma GCC optimize("tree-vectorize")
  #define here(...)
  #define com(msg)
  #define watch(...)
  #define watchVec(vec)
  #define local(x)
  #define alter(x,y) y
  #define M_assert(expr) do{if(!expr) vector<int> MLE_(3e8, 0);} while(0)
  #define O_assert(expr) do{if(!expr) while(1) printf("Hello, world!\n");} while(0)
#elif defined LOCAL_JUDGE
  #pragma GCC target("avx2")
  #pragma GCC optimize("tree-vectorize")
  #define here(...)
  #define com(msg)
  #define watch(...)
  #define watchVec(vec)
  #define local(x)
  #define alter(x,y) y
  #define M_assert(expr) assert(expr)
  #define O_assert(expr) assert(expr)
#else
  #define here(...) cerr<<"[Debug] "<<__func__<<"/"<<__LINE__<<" : reached "<<#__VA_ARGS__<<"\n"
  #define com(msg) cerr<<"[Debug] "<<msg<<"\n"
  #define watch(...) dbg1(#__VA_ARGS__,__VA_ARGS__)
  #define watchVec(vec) do{cerr<<#vec<<": ";dbg2(#vec,vec);} while(0)
  #define local(x) do{x} while(0)
  #define alter(x,y) x
  #define M_assert(expr) assert(expr)
  #define O_assert(expr) assert(expr)
#endif

#include <bits/stdc++.h>
#include <boost/range/irange.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <atcoder/all>
namespace ac = atcoder;

#define rep(i,n) for(int i=0;i<(n);i++)
#define rng(i,f,t,s) for(int i : boost::irange(f,t,s))
#define erng(i,f,t,s) for(int i : boost::irange(f,t+(s>0)-(s<0),s))
#define yes(n) cout<<((n)?YES:NO)<<"\n"
#define Sort(v) sort((v).begin(),(v).end())
#define rSort(v) sort((v).rbegin(),(v).rend())
#define Rev(v) reverse((v).begin(),(v).end())
#define Unique(v) (v).erase(unique((v).begin(),(v).end()),(v).end())
#define inarea(p,a,b) (0<=(p.first)&&0<=(p.second)&&(p.first)<(a)&&(p.second)<(b))
#define in_area(y,x,a,b) (0<=(y)&&0<=(x)&&(y)<(a)&&(x)<(b))
#define in_range(x,a,b) ((a)<=(x)&&(x)<(b))

using namespace std;
using Int = boost::multiprecision::cpp_int;

constexpr int           INF   = 1e9;
constexpr long long     LINF  = 1e18;
constexpr double        EPS   = 1e-9;
constexpr int           dy[]  = {1,0,-1,0,1,1,-1,-1};
constexpr int           dx[]  = {0,1,0,-1,-1,1,1,-1};
constexpr pair<int,int> dv[]  = { {1,0},{0,1},{-1,0},{0,-1},{1,-1},{1,1},{-1,1},{-1,-1} };

// --- functions for debugging --- //
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
template<class T>            void dbg1(const char *n, T &&v){cerr<<n<<": "<<v<<"\n";}
template<class S,class... T> void dbg1(const char *n, S &&v, T&&... w){const char *c=strchr(n+1,',');cerr.write(n,c-n)<<": "<<v<<" |";dbg1(c+1,w...);}
template<class T>            void dbg2(const char *n, vector<vector<T>> &v){bool f=0;for(const vector<T> &x:v){if(f)cerr<<string(strlen(n)+2,' ');f=1;dbg2<T>("",x);}}
template<class T>            void dbg2(const char *n, vector<deque<T>> &v){bool f=0;for(const deque<T> &x:v){if(f)cerr<<string(strlen(n)+2,' ');f=1;dbg2<T>("",x);}}
template<class T>            void dbg2(const char *n, vector<T> &v){cerr<<"[ ";for(auto &&x:v)cerr<<x<<" ";cerr<<"]\n";}
template<class T>            void dbg2(const char *n, deque<T> &v){cerr<<"[ ";for(const T &x:v)cerr<<x<<" ";cerr<<"]\n";}
template<class T>            void dbg2(const char *n, set<T> &v){cerr<<"[ ";for(const T &x:v)cerr<<x<<" ";cerr<<"]\n";}
template<class P>            void dbg2(const char *n, P &p){cerr<<"{ "<<p.first<<" "<<p.second<<" } ";}
template<class S,class T>    void dbg2(const char *n, map<S,T> &m){cerr<<"[ ";for(const pair<S,T> &p:m)cerr<<"{ "<<p.first<<", "<<p.second<<" } ";cerr<<"]\n";}
template<class S,class T>    void dbg2(const char *n, vector<pair<S,T>> &v){cerr<<"[ ";for(const pair<S,T> &p:v)dbg2<pair<S,T>>("",p);cerr<<"]\n";}
template<class S,class T>    void dbg2(const char *n, deque<pair<S,T>> &v){cerr<<"[ ";for(const pair<S,T> &p:v)dbg2<pair<S,T>>("",p);cerr<<"]\n";}
#pragma GCC diagnostic pop
// clang-format on
#pragma endregion

void solve(int N, int M, vector<int> A, vector<int> B) {
  ac::scc_graph g(N);

  rep(i, M) {
    g.add_edge(A[i], B[i]);
    g.add_edge(B[i], A[i]);
  }

  vector<vector<int>> x = g.scc();

  cout << (int) x.size() - 1 << "\n";
}

// clang-format off
int main() {
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wunused-result"
  int N;
  scanf("%d",&N);
  int M;
  scanf("%d",&M);
  vector<int> A(M);
  vector<int> B(M);
  for(int i = 0 ; i < M ; i++){
    scanf("%d",&A[i]);
    scanf("%d",&B[i]);
    A[i]--;
    B[i]--;
  }
  #pragma GCC diagnostic pop
  solve(N, M, move(A), move(B));
  exit(EXIT_SUCCESS);
}


Measured memory_bytes: 11992