// doot diddly donger cuckerino Hahahahahah


#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx")
#pragma GCC optimize("unroll-loops")

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

using namespace __gnu_pbds;
using namespace __gnu_cxx;
using namespace std;

using ll = long long;
using ld = long double;
using pll = pair<ll, ll>;
using vi = vector<int>;

template <typename T> using ordered_set = tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

const ld PI = acos(-1);

template <typename T> inline T mini(T& x, T y) { return x = min(x, y); }
template <typename T> inline T maxi(T& x, T y) { return x = max(x, y); }
mt19937 rnd(chrono::high_resolution_clock::now().time_since_epoch().count());
template <typename T, typename U>
std::ostream& operator<<(std::ostream& stream, const pair<T, U>& p) {
  return stream << p.first << " " << p.second;
}
template <typename T>
std::ostream& operator<<(std::ostream& out, const vector<T>& vec) {
  for (const T& x: vec) out << x << ' ';
  return out;
}
template <typename T>
std::istream& operator>>(std::istream& in, vector<T>& vec) {
  for(auto& x: vec) in >> x;
  return in;
}
// @formatter:on






#ifdef LOCAL
void signalHandler(int) {
  throw "Abort detected";
}
#endif

void doReplace() {
}

template <typename T, typename U, typename...Vs>
void doReplace(T &t, const U &u, Vs &&...vs) {
  t = u;
  doReplace(vs...);
}

template <typename T, typename...Us>
T minim(T &was, const T &cand, Us &&...us) {
  if (was > cand) {
    was = cand;
    doReplace(us...);
  }
  return was;
}

template <typename T, typename...Us>
T maxim(T &was, const T &cand, Us &&...us) {
  if (was < cand) {
    was = cand;
    doReplace(us...);
  }
  return was;
}

struct DSU {
  enum Heuristics {
    SIZE,
    RANK
  };
  
  int n;
  std::vector<int> par;
  std::vector<int> rank;
  Heuristics heuristics;
  
  explicit DSU(int _n, Heuristics _heuristics = RANK) :
      n(_n), heuristics(_heuristics) {
    par.resize(n);
    rank.resize(n);
    for (int i = 0; i < n; ++i) {
      par[i] = i;
      rank[i] = heuristics == SIZE;
    }
  }
  
  int root(int x) {
    return par[x] = (x == par[x]) ? x : root(par[x]);
  }
  
  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y) return true;
    if (rank[x] < rank[y]) swap(x, y);
    par[y] = x;
    if (heuristics == SIZE) {
      rank[x] += rank[y]; // Size use any one.
    } else {
      if (rank[x] == rank[y]) rank[x]++; // Rank Heuristics
    }
    return false;
  }
};

class CConnectCities {
public:
  
  void solve(std::istream& cin, std::ostream& cout) {
    int n, q;
    cin >> n >> q;
    DSU dsu(n);
    for (int i = 0; i < q; ++i) {
      int u, v;
      cin >> u >> v;
      dsu.unite(u - 1, v - 1);
    }
    set<int> st;
    for (int i = 0; i < n; ++i) {
      st.insert(dsu.root(i));
    }
    cout << (st.size() - 1) << '\n';
  }
};

int32_t main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
	CConnectCities solver;
	std::istream& in(std::cin);
	std::ostream& out(std::cout);
	solver.solve(in, out);
	return 0;
}


Measured memory_bytes: 7992