////////////////////////////////////////////////////////////////////////////////
//                          Give me AC!!!                                     //
////////////////////////////////////////////////////////////////////////////////
#include <bits/stdc++.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <cmath>
#include <iostream>
#include <limits>
#include <random>
using namespace std;
namespace mp = boost::multiprecision;
using namespace mp;
using ull = __int128;
using ll = long long;
using cll = cpp_int;
using ld = long double;
using Graph = vector<vector<int>>;
#define REP(i, n) for (ll i = 0; i < (ll)(n); i++)
#define REPD(i, n) for (ll i = n - 1; i >= 0; i--)
#define FOR(i, a, b) for (ll i = a; i <= (ll)(b); i++)
#define FORD(i, a, b) for (ll i = a; i >= (ll)(b); i--)
// xにはvectorなどのコンテナ
#define ALL(x) (x).begin(), (x).end() // sortなどの引数を省略したい
#define SIZE(x) ((ll)(x).size())      // sizeをsize_tからllに直しておく
#define MAX(x) *max_element(ALL(x))   // 最大値を求める
#define MIN(x) *min_element(ALL(x))   // 最小値を求める
#define PQ priority_queue<vector<ll>, vector<vector<ll>>, greater<vector<ll>>>
#define PB push_back // vectorヘの挿入
#define MP make_pair // pairのコンストラクタ
#define F first      // pairの一つ目の要素
#define S second     // pairの二つ目の要素
#define coutY cout << "YES" << endl
#define couty cout << "Yes" << endl
#define coutN cout << "NO" << endl
#define coutn cout << "No" << endl
#define coutdouble(a, b) cout << fixed << setprecision(a) << double(b);
#define vi(a, b) vector<int> a(b)
#define vl(a, b) vector<ll> a(b)
#define vs(a, b) vector<string> a(b)
#define vll(a, b, c) vector<vector<ll>> a(b, vector<ll>(c));
#define intque(a) queue<int> a;
#define llque(a) queue<ll> a;
#define intque2(a) priority_queue<int, vector<int>, greater<int>> a;
#define llque2(a) priority_queue<ll, vector<ll>, greater<ll>> a;
#define pushback(a, b) a.push_back(b)
#define mapii(M1) map<int, int> M1;
#define cou(v, x) count(v.begin(), v.end(), x)
#define mapll(M1) map<ll, ll> M1;
#define mapls(M1) map<ll, string> M1;
#define mapsl(M1) map<string, ll> M1;
#define twolook(a, l, r, x) lower_bound(a + l, a + r, x) - a
#define sor(a) sort(a.begin(), a.end())
#define rever(a) reverse(a.begin(), a.end())
#define rep(i, a) for (ll i = 0; i < a; i++)
#define vcin(n)                                                                \\
  for (ll i = 0; i < ll(n.size()); i++)                                        \\
  cin >> n[i]
#define vcout(n)                                                               \\
  for (ll i = 0; i < ll(n.size()); i++)                                        \\
  cout << n[i]
#define vcin2(n) rep(i, ll(n.size())) rep(j, ll(n.at(0).size())) cin >> n[i][j]
const ll mod = 998244353;
const ll MOD = 998244353;
// const ll MOD = 1000000007;
// const ll mod = 1000000007;
constexpr ll MAX = 5000000;
// const ll _max = 9223372036854775807;
const ll _max = 1223372036854775807;
const ll INF = 2000000000000000000;

ll fac[MAX], finv[MAX], inv[MAX];

// テーブルを作る前処理
void COMinit() {
  fac[0] = fac[1] = 1;
  finv[0] = finv[1] = 1;
  inv[1] = 1;
  for (int i = 2; i < MAX; i++) {
    fac[i] = fac[i - 1] * i % MOD;
    inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;
    finv[i] = finv[i - 1] * inv[i] % MOD;
  }
}

// 二項係数計算
long long COM(int n, int k) {
  if (n < k)
    return 0;
  if (n < 0 || k < 0)
    return 0;
  return fac[n] * (finv[k] * finv[n - k] % MOD) % MOD;
}

int modPow(long long a, long long n, long long p) {
  if (n == 0)
    return 1; // 0乗にも対応する場合
  if (n == 1)
    return a % p;
  if (n % 2 == 1)
    return (a * modPow(a, n - 1, p)) % p;
  long long t = modPow(a, n / 2, p);
  return (t * t) % p;
}

ll clocks(ll a, ll b, ll c) { return a * 3600 + b * 60 + c; }
ll divup(ll b, ll d) {
  if (b % d == 0) {
    return b / d;
  } else {
    return b / d + 1;
  }
}

struct Edge {
  int to;     // 辺の行き先
  int weight; // 辺の重み
  Edge(int t, int w) : to(t), weight(w) {}
};

using Graphw = vector<vector<Edge>>;
ll zero(ll a) { return max(ll(0), a); }

template <typename T> struct FormalPowerSeries : vector<T> {
  using vector<T>::vector;
  using P = FormalPowerSeries;

  using MULT = function<P(P, P)>;

  static MULT &get_mult() {
    static MULT mult = nullptr;
    return mult;
  }

  static void set_fft(MULT f) { get_mult() = f; }

  void shrink() {
    while (this->size() && this->back() == T(0))
      this->pop_back();
  }

  P operator+(const P &r) const { return P(*this) += r; }

  P operator+(const T &v) const { return P(*this) += v; }

  P operator-(const P &r) const { return P(*this) -= r; }

  P operator-(const T &v) const { return P(*this) -= v; }

  P operator*(const P &r) const { return P(*this) *= r; }

  P operator*(const T &v) const { return P(*this) *= v; }

  P operator/(const P &r) const { return P(*this) /= r; }

  P operator%(const P &r) const { return P(*this) %= r; }

  P &operator+=(const P &r) {
    if (r.size() > this->size())
      this->resize(r.size());
    for (int i = 0; i < r.size(); i++)
      (*this)[i] += r[i];
    return *this;
  }

  P &operator+=(const T &r) {
    if (this->empty())
      this->resize(1);
    (*this)[0] += r;
    return *this;
  }

  P &operator-=(const P &r) {
    if (r.size() > this->size())
      this->resize(r.size());
    for (int i = 0; i < r.size(); i++)
      (*this)[i] -= r[i];
    shrink();
    return *this;
  }

  P &operator-=(const T &r) {
    if (this->empty())
      this->resize(1);
    (*this)[0] -= r;
    shrink();
    return *this;
  }

  P &operator*=(const T &v) {
    const int n = (int)this->size();
    for (int k = 0; k < n; k++)
      (*this)[k] *= v;
    return *this;
  }

  P &operator*=(const P &r) {
    if (this->empty() || r.empty()) {
      this->clear();
      return *this;
    }
    assert(get_mult() != nullptr);
    return *this = get_mult()(*this, r);
  }

  P &operator%=(const P &r) { return *this -= *this / r * r; }

  P operator-() const {
    P ret(this->size());
    for (int i = 0; i < this->size(); i++)
      ret[i] = -(*this)[i];
    return ret;
  }

  P &operator/=(const P &r) {
    if (this->size() < r.size()) {
      this->clear();
      return *this;
    }
    int n = this->size() - r.size() + 1;
    return *this = (rev().pre(n) * r.rev().inv(n)).pre(n).rev(n);
  }

  P pre(int sz) const {
    return P(begin(*this), begin(*this) + min((int)this->size(), sz));
  }

  P operator>>(int sz) const {
    if (this->size() <= sz)
      return {};
    P ret(*this);
    ret.erase(ret.begin(), ret.begin() + sz);
    return ret;
  }

  P operator<<(int sz) const {
    P ret(*this);
    ret.insert(ret.begin(), sz, T(0));
    return ret;
  }

  P rev(int deg = -1) const {
    P ret(*this);
    if (deg != -1)
      ret.resize(deg, T(0));
    reverse(begin(ret), end(ret));
    return ret;
  }

  P diff() const {
    const int n = (int)this->size();
    P ret(max(0, n - 1));
    for (int i = 1; i < n; i++)
      ret[i - 1] = (*this)[i] * T(i);
    return ret;
  }

  P integral() const {
    const int n = (int)this->size();
    P ret(n + 1);
    ret[0] = T(0);
    for (int i = 0; i < n; i++)
      ret[i + 1] = (*this)[i] / T(i + 1);
    return ret;
  }

  // F(0) must not be 0
  P inv(int deg = -1) const {
    assert(((*this)[0]) != T(0));
    const int n = (int)this->size();
    if (deg == -1)
      deg = n;
    P ret({T(1) / (*this)[0]});
    for (int i = 1; i < deg; i <<= 1) {
      ret = (ret + ret - ret * ret * pre(i << 1)).pre(i << 1);
    }
    return ret.pre(deg);
  }

  // F(0) must be 1
  P log(int deg = -1) const {
    assert((*this)[0] == 1);
    const int n = (int)this->size();
    if (deg == -1)
      deg = n;
    return (this->diff() * this->inv(deg)).pre(deg - 1).integral();
  }

  P sqrt(int deg = -1) const {
    const int n = (int)this->size();
    if (deg == -1)
      deg = n;

    if ((*this)[0] == T(0)) {
      for (int i = 1; i < n; i++) {
        if ((*this)[i] != T(0)) {
          if (i & 1)
            return {};
          if (deg - i / 2 <= 0)
            break;
          auto ret = (*this >> i).sqrt(deg - i / 2) << (i / 2);
          if (ret.size() < deg)
            ret.resize(deg, T(0));
          return ret;
        }
      }
      return P(deg, 0);
    }

    P ret({T(1)});
    T inv2 = T(1) / T(2);
    for (int i = 1; i < deg; i <<= 1) {
      ret = (ret + pre(i << 1) * ret.inv(i << 1)) * inv2;
    }
    return ret.pre(deg);
  }

  // F(0) must be 0
  P exp(int deg = -1) const {
    assert((*this)[0] == T(0));
    const int n = (int)this->size();
    if (deg == -1)
      deg = n;
    P ret({T(1)});
    for (int i = 1; i < deg; i <<= 1) {
      ret = (ret * (pre(i << 1) + T(1) - ret.log(i << 1))).pre(i << 1);
    }
    return ret.pre(deg);
  }

  P pow(int64_t k, int deg = -1) const {
    const int n = (int)this->size();
    if (deg == -1)
      deg = n;
    for (int i = 0; i < n; i++) {
      if ((*this)[i] != T(0)) {
        T rev = T(1) / (*this)[i];
        P C(*this * rev);
        P D(n - i);
        for (int j = i; j < n; j++)
          D[j - i] = C[j];
        D = (D.log() * k).exp() * (*this)[i].pow(k);
        P E(deg);
        if (i * k > deg)
          return E;
        auto S = i * k;
        for (int j = 0; j + S < deg && j < D.size(); j++)
          E[j + S] = D[j];
        return E;
      }
    }
    return *this;
  }

  T eval(T x) const {
    T r = 0, w = 1;
    for (auto &v : *this) {
      r += w * v;
      w *= x;
    }
    return r;
  }
};

// aはbの何乗以下かを満たす数の内最大の物,(a,10)はaの桁数
ll expless(ll a, ll b) {
  ll k = 0;
  ll o = 1;
  while (a >= o) {
    k++;
    o = o * b;
  }
  return k;
}
// aをb進法で表す

// b進法のaを10進法に直す
ll tenbase(ll a, ll b) {
  ll c = expless(a, 10);
  ll ans = 0;
  ll k = 1;
  for (int i = 0; i < c; i++) {
    ans += (a % 10) * k;
    k = k * b;
    a = a / 10;
  }
  return ans;
}
vector<pair<long long, long long>> prime_factorize(long long N) {
  vector<pair<long long, long long>> res;
  for (long long a = 2; a * a <= N; ++a) {
    if (N % a != 0)
      continue;
    long long ex = 0; // 指数

    // 割れる限り割り続ける
    while (N % a == 0) {
      ++ex;
      N /= a;
    }

    // その結果を push
    res.push_back({a, ex});
  }

  // 最後に残った数について
  if (N != 1)
    res.push_back({N, 1});
  return res;
}
ll atll(ll a, ll b) {
  b++;
  ll c = expless(a, 10);
  ll d = c - b;
  ll f = 1;
  for (int i = 0; i < d; i++) {
    f = f * 10;
  }
  a = (a / f);
  return a % 10;
}
// aがbで何回割り切るか
ll exp(ll a, ll b) {
  ll ans = 0;
  while (a % b == 0) {
    a = a / b;
    ans++;
  }
  return ans;
}
const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, 1, 0, -1};
const int X[6] = {1, 1, 0, -1, -1, 0};
const int Y[6] = {0, 1, 1, 0, -1, -1};

template <typename T> vector<T> smallest_prime_factors(T n) {

  vector<T> spf(n + 1);
  for (int i = 0; i <= n; i++)
    spf[i] = i;

  for (T i = 2; i * i <= n; i++) {

    // 素数だったら
    if (spf[i] == i) {

      for (T j = i * i; j <= n; j += i) {

        // iを持つ整数かつまだ素数が決まっていないなら
        if (spf[j] == j) {
          spf[j] = i;
        }
      }
    }
  }

  return spf;
}

vector<pair<ll, ll>> factolization(ll x, vector<ll> &spf) {
  vector<pair<ll, ll>> ret;
  ll p;
  ll z;
  while (x != 1) {
    p = (spf[x]);
    z = 0;
    while (x % p == 0) {
      z++;
      x /= p;
    }
    ret.push_back({p, z});
  }
  return ret;
}
vector<bool> is;
vector<long long int> prime_(ll n) {
  is.resize(n + 1, true);
  is[0] = false;
  is[1] = false;

  vector<long long int> primes;
  for (int i = 2; i <= n; i++) {
    if (is[i] == true) {
      primes.push_back(i);
      for (int j = i * 2; j <= n; j += i) {
        is[j] = false;
      }
    }
  }
  return primes;
}
vector<ll> dijkstra(ll f, ll n, vector<vector<vector<ll>>> &edge) {
  // 最短経路としてどの頂点が確定済みかをチェックする配列
  vector<ll> confirm(n, false);
  // それぞれの頂点への最短距離を保存する配列
  // 始点は0,始点以外はINFで最短距離を初期化する
  vector<ll> mincost(n, INF);
  mincost[f] = 0;
  // 確定済みの頂点の集合から伸びる辺を伝ってたどり着く頂点の始点からの距離を短い順に保存するPriority
  // queue
  PQ mincand;
  mincand.push({mincost[f], f});

  // mincandの要素がゼロの時、最短距離を更新できる頂点がないことを示す
  while (!mincand.empty()) {
    // 最短距離でたどり着くと思われる頂点を取り出す
    vector<ll> next = mincand.top();
    mincand.pop();
    // すでにその頂点への最短距離が確定済みの場合は飛ばす
    if (confirm[next[1]])
      continue;
    // 確定済みではない場合は確定済みにする
    confirm[next[1]] = true;
    // その確定済みの頂点から伸びる辺の情報をとってくる(参照の方が速い)、lは辺の本数
    vector<vector<ll>> &v = edge[next[1]];
    ll l = SIZE(v);
    REP(i, l) {
      // 辺の先が確定済みなら更新する必要がない((✳2)があれば十分なので(✳1)は実はいらない)
      if (confirm[v[i][0]])
        continue; //(✳1)
      // 辺の先のmincost以上の場合は更新する必要がない(辺の先が確定済みの時は満たす)
      if (mincost[v[i][0]] <= next[0] + v[i][1])
        continue; //(✳2)
      // 更新
      mincost[v[i][0]] = next[0] + v[i][1];
      // 更新した場合はその頂点が(確定済みでない頂点の中で)最短距離になる可能性があるのでmincandに挿入
      mincand.push({mincost[v[i][0]], v[i][0]});
    }
  }
  return mincost;
}
ll so(ll a) {
  ll ans = 0;
  if (a == 0) {
    return 0;
  }
  while (a % 2 == 0) {
    a /= 2;
    ans++;
  }
  return ans;
}
ll HOM(ll n, ll r) { return COM(n + r - 1, r); }
ll binary(ll bina) {
  ll ans = 0;
  for (ll i = 0; bina > 0; i++) {
    ans = ans + (bina % 2) * pow(10, i);
    bina = bina / 2;
  }
  return ans;
}
struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};

vector<long long> enum_divisors(long long N) {
  vector<long long> res;
  for (long long i = 1; i * i <= N; ++i) {
    if (N % i == 0) {
      res.push_back(i);
      // 重複しないならば i の相方である N/i も push
      if (N / i != i)
        res.push_back(N / i);
    }
  }
  // 小さい順に並び替える
  sort(res.begin(), res.end());
  return res;
}
vector<ll> zaatu(vector<ll> a, ll n) {
  map<ll, ll> mp;
  for (int i = 0; i < n; i++) {
    cin >> a[i];
    mp[a[i]] = 0;
  }

  // 小さい値から順に番号を付けていく
  ll num = 0;
  for (auto x : mp) {
    mp[x.first] = num;
    num++;
  }
  vector<ll> ans;
  for (int i = 0; i < n; i++) {
    ans.push_back(mp[a[i]]);
  }
  return ans;
}
ll vectorcheck(vector<ll> t, ll key) {
  auto iter = lower_bound(ALL(t), key);
  auto iter2 = upper_bound(ALL(t), key);
  if ((iter - t.begin()) != (iter2 - t.begin())) {
    return 1;
  } else {
    return 0;
  }
}
template <class Monoid> struct SegmentTree {
  using T = typename Monoid::value_type;

  std::vector<T> tree;

  SegmentTree() = default;
  explicit SegmentTree(ll n) : tree(n << 1, Monoid::identity()){};

  template <class InputIterator>
  SegmentTree(InputIterator first, InputIterator last) {
    tree.assign(distance(first, last) << 1, Monoid::identity());

    ll i;
    i = size();
    for (InputIterator itr = first; itr != last; itr++) {
      tree[i++] = *itr;
    }
    for (i = size() - 1; i > 0; i--) {
      tree[i] = Monoid::operation(tree[(i << 1)], tree[(i << 1) | 1]);
    }
  };

  inline ll size() { return tree.size() >> 1; };

  inline T operator[](ll k) { return tree[k + size()]; };
  void add(ll k, const T dat) {
    k += size();
    tree[k] += dat;

    while (k > 1) {
      k >>= 1;
      tree[k] = Monoid::operation(tree[(k << 1)], tree[(k << 1) | 1]);
    }
  };
  void update(ll k, const T dat) {
    k += size();
    tree[k] = dat;

    while (k > 1) {
      k >>= 1;
      tree[k] = Monoid::operation(tree[(k << 1)], tree[(k << 1) | 1]);
    }
  };

  T fold(ll l, ll r) {
    l += size(); // points leaf
    r += size();

    T lv = Monoid::identity();
    T rv = Monoid::identity();
    while (l < r) {
      if (l & 1)
        lv = Monoid::operation(lv, tree[l++]);
      if (r & 1)
        rv = Monoid::operation(tree[--r], rv);
      l >>= 1;
      r >>= 1;
    }

    return Monoid::operation(lv, rv);
  };

  template <class F> inline ll sub_tree_search(ll i, T sum, F f) {
    while (i < size()) {
      T x = Monoid::operation(sum, tree[i << 1]);
      if (f(x)) {
        i = i << 1;
      } else {
        sum = x;
        i = (i << 1) | 1;
      }
    }
    return i - size();
  }

  template <class F> ll search(ll l, F f) {
    l += size();
    ll r = size() * 2; // r = n;
    ll tmpr = r;
    ll shift = 0;

    T sum = Monoid::identity();
    while (l < r) {
      if (l & 1) {
        if (f(Monoid::operation(sum, tree[l]))) {
          return sub_tree_search(l, sum, f);
        }
        sum = Monoid::operation(sum, tree[l]);
        l++;
      }
      l >>= 1;
      r >>= 1;
      shift++;
    }

    while (shift > 0) {
      shift--;
      r = tmpr >> shift;
      if (r & 1) {
        r--;
        if (f(Monoid::operation(sum, tree[r]))) {
          return sub_tree_search(r, sum, f);
        }
        sum = Monoid::operation(sum, tree[r]);
      }
    }

    return -1;
  };
};

using namespace std;
using llong = long long;

struct Monoid {
  using value_type = ll;
  inline static ll identity() { return 0; };
  inline static ll operation(ll a, ll b) { return a + b; };
};
int main() {
  ios::sync_with_stdio(false);
  std::cin.tie(nullptr);
  cout << fixed << setprecision(30);
  int n, q;
  cin >> n >> q;

  UnionFind tree(n);
  for (int i = 0; i < q; i++) {
    ll x, y;
    cin >> x >> y;
    tree.unite(x, y);
  }
  vector<ll> a(n + 100);
  for (int i = 0; i < n; i++) {
    a.at(tree.root(i + 1) + 1)++;
  }
  ll ans = 0;
  for (int i = 0; i < n + 100; i++) {
    if (a.at(i) > 0) {
      ans++;
    }
  }
  cout << ans - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Edge {
  int to;
};
using Graph = vector<vector<Edge>>;
// 深さ優先探索
vector<bool> seen; // 探索したか記録
void dfs(const Graph &G, int v) {
  seen[v] = true;
  for (auto e : G[v]) {
    if (!seen[e.to]) { // 訪問済みでなければ探索
      dfs(G, e.to);
    }
  }
}
int main() {
  ll n, m;
  cin >> n >> m;
  Graph G(n);
  for (ll i = 0; i < m; i++) {
    ll a, b;
    cin >> a >> b;
    a--;
    b--;
    G[a].push_back({b});
    G[b].push_back({a});
  }
  seen.assign(n, false); // 初期化
  ll cnt = 0;
  for (ll i = 0; i < n; i++) {
    if (!seen[i]) {
      dfs(G, i);
      cnt++; // dfsした回数をカウント
    }
  }
  cout << cnt - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>

using namespace std;
using ll = long long;
using ld = long double;
const int MOD = 1e9 + 7;

struct UnionFind {
  vector<int> par;
  vector<int> count;

  UnionFind(int N) : par(N), count(N) {
    for (int i = 0; i < N; i++) {
      par[i] = i;
      count[i] = 1;
    }
  }

  int root(int x) {
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }
  int getCount(int x) { return count[x]; }

  void unite(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return;
    if (count[rx] < count[ry]) {
      count[ry] += count[rx];
      par[rx] = ry;
    } else {
      count[rx] += count[ry];
      par[ry] = rx;
    }
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  auto u = UnionFind(n);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    u.unite(a, b);
  }
  set<int> s;
  for (int i = 0; i < n; i++) {
    s.insert(u.root(i));
  }
  cout << n - s.size() << endl;
}

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
using ld = long double;
const int MOD = 1e9 + 7;

struct UnionFind {
  vector<int> par;
  vector<int> count;

  UnionFind(int N) : par(N), count(N) {
    for (int i = 0; i < N; i++) {
      par[i] = i;
      count[i] = 1;
    }
  }

  int root(int x) {
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }
  int getCount(int x) { return count[x]; }

  void unite(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return;
    if (count[rx] < count[ry]) {
      count[ry] += count[rx];
      par[rx] = ry;
    } else {
      count[rx] += count[ry];
      par[ry] = rx;
    }
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  auto u = UnionFind(n);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    u.unite(a, b);
  }
  set<int> s;
  for (int i = 0; i < n; i++) {
    s.insert(u.root(i));
  }
  cout << s.size() - 1 << endl;
}


#include <bits/stdc++.h>

using namespace std;

int p[10010];
int n, m;
int cnt[100010];
int ans;

int fifa(int x) {
  int t = x;
  while (p[t] != t) {
    t = p[t];
  }
  while (p[x] != t) {
    int tmp = p[x];
    p[x] = t;
    x = tmp;
  }
  return t;
}

void join(int x, int y) {
  int px = fifa(x);
  int py = fifa(y);
  if (px != py) {
    p[px] = py;
  }
}

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) {
    p[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    int a, b;
    scanf("%d%d", &a, &b);
    join(a, b);
  }
  for (int i = 1; i <= n; i++) {
    cnt[fifa(i)]++;
  }
  for (int i = 1; i <= n; i++) {
    if (cnt[fifa(i)] == 1) {
      ans++;
    }
  }
  printf("%d\\n", ans);
  return 0;
}

#include <bits/stdc++.h>

using namespace std;

int p[100010];
int n, m;
int cnt[100010];
int ans;

int fifa(int x) {
  int t = x;
  while (p[t] != t) {
    t = p[t];
  }
  while (p[x] != t) {
    int tmp = p[x];
    p[x] = t;
    x = tmp;
  }
  return t;
}

void join(int x, int y) {
  int px = fifa(x);
  int py = fifa(y);
  if (px != py) {
    p[px] = py;
  }
}

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) {
    p[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    int a, b;
    scanf("%d%d", &a, &b);
    join(a, b);
  }
  for (int i = 1; i <= n; i++) {
    if (fifa(i) == i) {
      ans++;
    }
  }
  printf("%d\\n", ans - 1);
  return 0;
}


#include <bits/stdc++.h>
#include <iostream>

using namespace std;

int main() {
  int N, M;
  cin >> N >> M;
  std::vector<bool> v(N + 1, false);
  std::vector<std::vector<bool>> adj(N + 1, v);

  int ai, bi;
  for (int i = 0; i < M; ++i) {
    cin >> ai >> bi;

    adj[ai][bi] = true;
    adj[bi][ai] = true;
  }

  for (int i = 1; i <= N; ++i) {
    adj[i][i] = 1;
  }

  int res = 0;

  bool *visited = (bool *)malloc(sizeof(bool) * (N + 1));
  memset(visited, false, N + 1);

  for (int i = 1; i <= N; ++i) {
    if (visited[i])
      continue;

    std::stack<int> v;
    v.push(i);

    while (!v.empty()) {

      int x = v.top();
      v.pop();

      visited[x] = true;

      for (int j = 1; j <= N; ++j) {
        if (adj[x][j])
          if (!visited[j])
            v.push(j);
      }
    }
    res += 1;
  }

  cout << res - 1 << endl;

  return 0;
}

#include <bits/stdc++.h>
#include <iostream>

using namespace std;

int main() {
  int N, M;
  cin >> N >> M;
  std::vector<std::vector<int>> adj(N + 1);

  int ai, bi;
  for (int i = 0; i < M; ++i) {
    cin >> ai >> bi;

    adj[ai].push_back(bi);
    adj[bi].push_back(ai);
  }

  // for (int i = 1; i <= N; ++i) {
  // 	adj[i][i] = 1;
  // }

  // for(int i = 1; i <= N; ++i) {
  // 	for (int j = 1; j <= N; ++j)
  // 		cout << adj[i][j] << " ";
  // 	cout << endl;
  // }

  int res = 0;

  bool *visited = (bool *)malloc(sizeof(bool) * (N + 1));
  memset(visited, false, N + 1);

  // cout << "visited: ";
  // for(int i = 1; i <= N; ++i) {
  // 	cout << visited[i] << " ";
  // }
  // cout << endl;

  for (int i = 1; i <= N; ++i) {
    if (visited[i])
      continue;
    // cout << "visiting " << i << endl;
    std::stack<int> v;
    v.push(i);

    while (!v.empty()) {

      int x = v.top();
      v.pop();

      visited[x] = true;
      // cout << "visited[" << x << "] = " << visited[x] << endl;

      for (auto ip = adj[x].begin(); ip != adj[x].end(); ++ip) {
        if (!visited[*ip])
          v.push(*ip);
      }
    }
    res += 1;
  }

  // cout << "After, visited: ";
  // for(int i = 1; i <= N; ++i) {
  // 	cout << visited[i] << " ";
  // }
  // cout << endl;

  cout << res - 1 << endl;

  return 0;
}


#include <bits/stdc++.h>

using namespace std;

int main() {
  int n, m, nrg = 0;
  cin >> n >> m;
  int **lista = (int **)calloc(n + 1, sizeof(int *));
  for (int i = 0; i <= n; ++i)
    lista[i] = (int *)calloc(1, sizeof(int));
  for (; m; --m) {
    int a, b;
    cin >> a >> b;
    int num = lista[a][0];
    lista[a] = (int *)realloc(lista[a], (num + 1) * sizeof(int));
    ++num;
    lista[a][num] = b;
    lista[a][0] = num;

    num = lista[b][0];
    lista[b] = (int *)realloc(lista[b], (num + 1) * sizeof(int));
    ++num;
    lista[b][num] = a;
    lista[b][0] = num;
  }
  vector<int> c(n + 1), viz(n + 1);
  for (int i = 1; i <= n; ++i)
    if (viz[i] == 0) {
      ++nrg;
      int st = 0, dr = 0;
      c[st] = i;
      viz[i] = 1;
      while (st <= dr) {
        int prim = c[st];
        for (int j = 1; j <= lista[prim][0]; ++j) {
          int nod = lista[prim][j];
          if (viz[nod] == 0) {
            viz[nod] = 1;
            ++dr;
            c[dr] = nod;
          }
        }
        ++st;
      }
    }
  cout << nrg - 1;
}

#include <bits/stdc++.h>

using namespace std;

typedef struct node {
  int key;
  struct node *next;
} Node;

Node *creeazaNod(int k) {
  Node *p = (Node *)malloc(sizeof(Node));
  p->key = k;
  p->next = NULL;
  return p;
}

void insert_last(Node **first, Node **last, int givenKey) {
  Node *p = creeazaNod(givenKey);
  if (*first == NULL)
    *first = *last = p;
  else {
    p->next = NULL;
    (*last)->next = p;
    *last = p;
  }
}

int main() {
  int n, m, nrg = 0;
  cin >> n >> m;
  vector<Node *> fst(n + 1);
  vector<Node *> lst(n + 1);
  for (; m; --m) {
    int a, b;
    cin >> a >> b;
    insert_last(&fst[a], &lst[a], b);
    insert_last(&fst[b], &lst[b], a);
  }
  vector<int> c(n + 1), viz(n + 1);
  for (int i = 1; i <= n; ++i)
    if (viz[i] == 0) {
      ++nrg;
      int st = 0, dr = 0;
      c[st] = i;
      viz[i] = 1;
      while (st <= dr) {
        int prim = c[st];
        for (Node *p = fst[prim]; p != NULL; p = p->next) {
          int nod = p->key;
          if (viz[nod] == 0) {
            viz[nod] = 1;
            ++dr;
            c[dr] = nod;
          }
        }
        ++st;
      }
    }
  cout << nrg - 1;
}


#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
#define _USE_MATH_DEFINES
#include <math.h>
#define NIL = -1;
#define all(x) x.begin(), x.end()
const ll INF = 1e9;
const ll mod = 1e9 + 7;

int digit(ll x) {
  if (x / 10 == 0)
    return 1;
  return digit(x / 10) + 1;
}

ll gcd(long long a, long long b) {
  if (a < b)
    swap(a, b);
  if (b == 0)
    return a;

  return gcd(b, a % b);
}

bool is_prime(long long N) {
  if (N == 1)
    return false;
  for (long long i = 2; i * i <= N; i++) {
    if (N % i == 0)
      return false;
  }
  return true;
}

ll lcm(ll a, ll b) { return ((a * b == 0)) ? 0 : (a / gcd(a, b) * b); }

double DegreeToRadian(double degree) { return degree * M_PI / 180.0; }

int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};

///////////////////////////////////////////////////////////////////////////////////

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> seen(n);
  vector<vector<int>> rep(n, vector<int>(n));
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    rep[a].push_back(b);
    rep[b].push_back(a);
  }

  queue<int> q;
  q.push(0);
  int cnt = 0;
  for (int i = 0; i < n; i++)
    seen[i] = 0;
  while (!q.empty()) {
    int nx = q.front();
    q.pop();
    if (seen[nx])
      continue;
    cnt++;
    for (auto v : rep[nx]) {
      seen[v]++;
      q.push(v);
    }
  }

  cout << cnt << endl;
}

#include <algorithm>
#include <iostream>
#include <map>
#include <math.h>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <utility>
#include <vector>
typedef long long ll;
const int dx[8] = {1, 0, -1, 0, 1, -1, -1, 1};
const int dy[8] = {0, 1, 0, -1, 1, 1, -1, -1};
const int dr[4] = {-1, 0, 1, 0};
const int dc[4] = {0, 1, 0, -1};
const int INF = 1e9 + 5;
#define FOR(i, a, n) for (int i = (int)(a); i < (int)(n); ++i)
#define REP(i, n) FOR(i, 0, n)
#define SORT(a) sort(a.begin(), a.end())
#define REVERSE(a) reverse(a.begin(), a.end())
using namespace std;
int n, m, u, v;
int memo[110000];
bool fl[110000];
vector<int> eg[110000];
int dp(int v) {
  if (fl[v]) {
    return memo[v];
  }
  int ans = 0;
  for (int to : eg[v]) {
    ans = max(ans, dp(to) + 1);
  }
  fl[v] = true;
  memo[v] = ans;
  return ans;
}
struct UnionFind {
  vector<int> d;
  UnionFind(int n = 0) : d(n, -1) {}
  int Find(int x) { // 根の番号を探す
    if (d[x] < 0)
      return x;
    return d[x] = Find(d[x]);
  }
  bool unite(int x, int y) {
    x = Find(x);
    y = Find(y);
    if (x == y)
      return false;
    if (d[x] < d[y]) {
      swap(x, y);
    }
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  bool same(int x, int y) { return Find(x) == Find(y); }
  int size(int x) { return (-d[Find(x)]); }
};
using namespace std;
int main() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n + 1);
  REP(i, m) {
    int a, b;
    cin >> a >> b;
    uf.unite(a, b);
    uf.unite(b, a);
  }
  set<int> st;
  for (int i = 1; i <= n; i++) {
    st.insert(uf.Find(i));
  }
  cout << st.size() - 1 << endl;
}


#include <bits/stdc++.h>
typedef long long int ll;

using namespace std;
const double PI = 3.14159265358979323846;

void bfs(vector<int> &visit, vector<vector<int>> &G, int s) {

  visit[s] = 1;

  for (int v : G[s]) {

    if (visit[v] == 0) {

      visit[v] = 1;
      bfs(visit, G, v);
    }
  }
}

int main() {
  int N, M;
  cin >> N >> M;

  vector<vector<int>> G(N, vector<int>(0));

  for (int i = 0; i < M; i++) {
    int A, B;

    cin >> A >> B;

    G[A - 1].push_back(B - 1);
    G[B - 1].push_back(A - 1);
  }
  vector<int> visit(N, 0);

  int count = 0;

  int s_i = 0;
  while (s_i != -1) {
    count++;
    bfs(visit, G, s_i);

    s_i = -1;
    for (int i = 0; i < N; i++) {

      if (visit[i] == 0) {

        s_i = i;
        break;
      }
    }
  }

  cout << count - 1 << endl;

  return 0;
}

#include <bits/stdc++.h>
typedef long long int ll;

using namespace std;
const double PI = 3.14159265358979323846;

void bfs(vector<int> &visit, vector<vector<int>> &G, int s, int &count) {

  visit[s] = 1;

  for (int v : G[s]) {

    if (visit[v] == 0) {

      visit[v] = 1;
      count++;
      bfs(visit, G, v, count);
    }
  }
}

int main() {
  int N, M;
  cin >> N >> M;

  vector<vector<int>> G(N, vector<int>(0));

  for (int i = 0; i < M; i++) {
    int A, B;

    cin >> A >> B;

    G[A - 1].push_back(B - 1);
    G[B - 1].push_back(A - 1);
  }
  vector<int> visit(N, 0);

  int count = 0;

  int s_i = 0;

  int c = 0;

  for (int i = 0; i < N; i++) {

    if (visit[i] == 0) {

      count++;
      bfs(visit, G, i, c);
    }
  }

  cout << count - 1 << endl;

  return 0;
}


#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (int i = 0; i < (n); i++)
using ll = long long;
using P = pair<int, int>;
using Graph = vector<vector<int>>;
int main() {
  int n, m;
  cin >> n >> m;
  Graph g(n + 1);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  vector<int> dist(n, -1);
  queue<int> que;
  int group = 0;
  rep(i, n + 1) {
    if (dist[i] == 1)
      continue;
    dist[i] = 1;
    que.push(i);
    while (!que.empty()) {
      int v = que.front();
      que.pop();
      for (int nv : g[v]) {
        if (dist[nv] == 1)
          continue;

        dist[nv] = 1;
        que.push(nv);
      }
    }
    group++;
  }
  cout << group - 1 << endl;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (int i = 0; i < (n); i++)
using ll = long long;
using P = pair<int, int>;
using Graph = vector<vector<int>>;
int main() {
  int n, m;
  cin >> n >> m;
  Graph g(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  vector<int> dist(n, -1);
  queue<int> que;
  int group = 0;
  rep(i, n) {
    if (dist[i] == 1)
      continue;
    dist[i] = 1;
    que.push(i);
    while (!que.empty()) {
      int v = que.front();
      que.pop();
      for (int nv : g[v]) {
        if (dist[nv] == 1)
          continue;

        dist[nv] = 1;
        que.push(nv);
      }
    }
    group++;
  }
  cout << group - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

void dfs(vector<unordered_set<ll>> adj, ll sv, ll n, bool *visited) {
  visited[sv] = true;
  for (auto it = adj[sv].begin(); it != adj[sv].end(); it++) {
    if (visited[*it] == false) {
      dfs(adj, (*it), n, visited);
    }
  }
}
ll cnt(vector<unordered_set<ll>> adj, ll n) {
  bool *visited = new bool[n];
  for (ll i = 0; i < n; i++) {
    visited[i] = false;
  }

  ll ct = 0;
  for (ll i = 0; i < n; i++) {
    if (visited[i] == false) {
      dfs(adj, i, n, visited);
      ct++;
    }
  }
  return ct;
}
int main() {
  ll n;
  cin >> n;
  ll m;
  cin >> m;

  vector<unordered_set<ll>> adj(n);

  for (ll i = 0; i < m; i++) {
    ll x, y;
    cin >> x >> y;
    adj[x - 1].insert(y - 1);
    adj[y - 1].insert(x - 1);
  }
  ll ct = cnt(adj, n);
  cout << ct - 1;
}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

void dfs(vector<int> adj[], int sv, bool *visited) {
  visited[sv] = true;
  for (int i = 0; i < adj[sv].size(); i++) {
    if (visited[adj[sv][i]] == false) {
      dfs(adj, adj[sv][i], visited);
    }
  }
}
int cnt(vector<int> adj[], int n) {
  bool *visited = new bool[n];
  for (int i = 0; i < n; i++) {
    visited[i] = false;
  }

  int ct = 0;
  for (int i = 0; i < n; i++) {
    if (visited[i] == false) {
      dfs(adj, i, visited);
      ct++;
    }
  }
  return ct;
}
int main() {
  int n;
  cin >> n;
  int m;
  cin >> m;

  vector<int> adj[n];

  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    adj[x - 1].push_back(y - 1);
    adj[y - 1].push_back(x - 1);
  }

  int ct = cnt(adj, n);
  cout << ct - 1;
}


#include <bits/stdc++.h>
using namespace std;
#define lli long long int
#define fastio                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);
#define test                                                                   \\
  lli t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define vll vector<lli>
#define mll map<lli, lli>
#define vvll vector<vector<lli>>
#define vpll vector<pair<lli, lli>>
#define vvpll vector<vector<pair<lli, lli>>>
#define mpll map<pair<lli, lli>, lli>
#define pll pair<lli, lli>
#define sll stack<lli>
#define qll queqe<lli>
#define pb push_back
#define bs binary_search
#define lb lower_bound
#define ub upper_bound
#define ff first
#define ss second
#define mod 1000000007
#define ma 1000000000000000000
#define mi -1000000000000000000
lli vis[1001] = {0};
vll adj[1001];
queue<lli> q;
void explore(lli n) {
  while (!q.empty()) {
    lli s = q.front();
    q.pop();
    for (auto i : adj[s]) {
      if (vis[i] == 0) {
        vis[i] = 1;
        q.push(i);
      }
    }
  }
}

int main() {

  fastio lli n, m, i, j, u, v, d = 0, a;
  cin >> n >> m;
  for (i = 0; i < m; i++) {
    cin >> u >> v;
    adj[u].pb(v);
    adj[v].pb(u);
  }

  for (i = 1; i <= n; i++) {
    if (vis[i] == 0) {
      vis[i] = 1;
      q.push(i);
      explore(i);
      d++;
    }
  }
  cout << d - 1;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define lli long long int
#define fastio                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);
#define test                                                                   \\
  lli t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define vll vector<lli>
#define mll map<lli, lli>
#define vvll vector<vector<lli>>
#define vpll vector<pair<lli, lli>>
#define vvpll vector<vector<pair<lli, lli>>>
#define mpll map<pair<lli, lli>, lli>
#define pll pair<lli, lli>
#define sll stack<lli>
#define qll queqe<lli>
#define pb push_back
#define bs binary_search
#define lb lower_bound
#define ub upper_bound
#define ff first
#define ss second
#define mod 1000000007
#define ma 1000000000000000000
#define mi -1000000000000000000
lli vis[100001] = {0};
vll adj[100001];
queue<lli> q;
void explore(lli n) {
  while (!q.empty()) {
    lli s = q.front();
    q.pop();
    for (auto i : adj[s]) {
      if (vis[i] == 0) {
        vis[i] = 1;
        q.push(i);
      }
    }
  }
}

int main() {

  fastio lli n, m, i, j, u, v, d = 0, a;
  cin >> n >> m;
  for (i = 0; i < m; i++) {
    cin >> u >> v;
    adj[u].pb(v);
    adj[v].pb(u);
  }

  for (i = 1; i <= n; i++) {
    if (vis[i] == 0) {
      vis[i] = 1;
      q.push(i);
      explore(i);
      d++;
    }
  }
  cout << d - 1;
  return 0;
}


// #pragma GCC optimize(2)
#include <algorithm>
#include <bits/stdc++.h>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
typedef long long ll;
#define db double
#define pb push_back
#define pa pair<signed, signed>
#define mp make_pair
#define sc(z) scanf("%d", &(z))
#define _ff(i, a, b) for (signed i = (a); i <= (b); ++i)
#define _rr(i, a, b) for (signed i = (a); i >= (b); --i)
#define _f(i, a, b) for (signed i = (a); i < (b); ++i)
#define _r(i, a, b) for (signed i = (a); i > (b); --i)
#define lson node << 1, l, mid
#define rson node << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
const signed inf = 0x3f3f3f3f;
const signed maxn = 1e5 + 5;
vector<int> G[maxn];
int tot, v[maxn];
void dfs(int u, int w) {
  v[u] = 1;
  _f(i, 0, G[u].size()) {
    int k = G[u][i];
    if (k == w)
      continue;
    dfs(k, u);
  }
}
int main() {
  int n, m;
  sc(n), sc(m);
  int u, w;
  while (m--) {
    sc(u), sc(w);
    G[u].pb(w);
    G[w].pb(u);
  }
  _ff(i, 1, n) {
    if (v[i])
      continue;
    tot++;
    dfs(i, 0);
  }
  printf("%d\\n", tot - 1);
  return 0;
}

// #pragma GCC optimize(2)
#include <algorithm>
#include <bits/stdc++.h>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <vector>
using namespace std;
typedef long long ll;
#define db double
#define pb push_back
#define pa pair<signed, signed>
#define mp make_pair
#define sc(z) scanf("%d", &(z))
#define _ff(i, a, b) for (signed i = (a); i <= (b); ++i)
#define _rr(i, a, b) for (signed i = (a); i >= (b); --i)
#define _f(i, a, b) for (signed i = (a); i < (b); ++i)
#define _r(i, a, b) for (signed i = (a); i > (b); --i)
#define lson node << 1, l, mid
#define rson node << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
const signed inf = 0x3f3f3f3f;
const signed maxn = 1e5 + 5;
vector<int> G[maxn];
int tot, v[maxn];
void dfs(int u) {
  v[u] = 1;
  _f(i, 0, G[u].size()) {
    int k = G[u][i];
    if (v[k])
      continue;
    dfs(k);
  }
}
int main() {
  memset(v, 0, sizeof(v));
  int n, m;
  sc(n), sc(m);
  int u, w;
  while (m--) {
    sc(u), sc(w);
    G[u].pb(w);
    G[w].pb(u);
  }
  _ff(i, 1, n) {
    if (v[i])
      continue;
    tot++;
    dfs(i);
  }
  printf("%d\\n", tot - 1);
  return 0;
}


#include <bits/stdc++.h>
using namespace std;

struct UnionFind {
  vector<int> par;
  int count = 0;
  UnionFind(int n) : par(n) {
    for (int i = 0; i < n; i++) {
      par[i] = i;
      if (i == 0)
        count = n;
    }
  }

  int root(int x) {
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return;
    par[rx] = ry;
    count--;
  }

  bool same(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }

  int ans() { return count - 1; }
};

int main() {
  int n, m;
  cin >> n >> m;

  UnionFind tree(n);

  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    tree.unite(a, b);
  }

  cout << tree.ans() << endl;
}

#include <bits/stdc++.h>
#define ll long long
using namespace std;

struct UnionFind {
  vector<ll> par;
  ll count = 0;
  UnionFind(ll n) : par(n) {
    for (ll i = 0; i < n; i++) {
      par[i] = i;
      if (i == 0)
        count = n;
    }
  }

  ll root(ll x) {
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(ll x, ll y) {
    ll rx = root(x);
    ll ry = root(y);
    if (rx == ry)
      return;
    par[rx] = ry;
    count--;
  }

  bool same(ll x, ll y) {
    ll rx = root(x);
    ll ry = root(y);
    return rx == ry;
  }

  ll ans() { return count - 1; }
};

int main() {
  ll n, m;
  cin >> n >> m;

  UnionFind tree(n);

  for (ll i = 0; i < m; i++) {
    ll a, b;
    cin >> a >> b;
    tree.unite(a, b);
  }

  cout << tree.ans() << endl;
}


#include <bits/stdc++.h>
using namespace std;

vector<vector<int>> adj;
vector<bool> vis;

void dfs(int v, int p) {
  vis[v] = 1;

  for (auto x : adj[v]) {
    if (x != p)
      dfs(x, v);
  }
}

signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);

  int n, m;
  cin >> n >> m;
  adj = vector<vector<int>>(n);
  vis = vector<bool>(n, false);

  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;

    adj[a].push_back(b);
    adj[b].push_back(a);
  }

  int ans = 0;

  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      dfs(i, -1);
      ans++;
    }
  }

  cout << ans - 1;
}

#include <bits/stdc++.h>
using namespace std;

vector<vector<int>> adj;
vector<bool> vis;

void dfs(int v, int p) {
  vis[v] = 1;

  for (auto x : adj[v]) {
    if (x != p && !vis[x])
      dfs(x, v);
  }
}

signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);

  int n, m;
  cin >> n >> m;
  adj = vector<vector<int>>(n);
  vis = vector<bool>(n, false);

  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;

    adj[a].push_back(b);
    adj[b].push_back(a);
  }

  int ans = 0;

  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      dfs(i, -1);
      ans++;
    }
  }

  cout << ans - 1;
}


#include <bits/stdc++.h>

using namespace std;
#define local
#ifdef local

template <class T> void _E(T x) { cerr << x; }

void _E(double x) { cerr << fixed << setprecision(6) << x; }

void _E(string s) { cerr << "\\"" << s << "\\""; }

template <class A, class B> void _E(pair<A, B> x) {
  cerr << '(';
  _E(x.first);
  cerr << ", ";
  _E(x.second);
  cerr << ")";
}

template <class T> void _E(vector<T> x) {
  cerr << "[";
  for (auto it = x.begin(); it != x.end(); ++it) {
    if (it != x.begin())
      cerr << ", ";
    _E(*it);
  }
  cerr << "]";
}

void ERR() {}

template <class A, class... B> void ERR(A x, B... y) {
  _E(x);
  cerr << (sizeof...(y) ? ", " : " ");
  ERR(y...);
}

#define debug(x...)                                                            \\
  do {                                                                         \\
    cerr << "{ " #x " } -> { ";                                                \\
    ERR(x);                                                                    \\
    cerr << "}" << endl;                                                       \\
  } while (false)
#else
#define debug(...) 114514.1919810
#endif
#define _rep(n, a, b) for (int n = (a); n <= (b); ++n)
#define _rev(n, a, b) for (int n = (a); n >= (b); --n)
#define _for(n, a, b) for (int n = (a); n < (b); ++n)
#define _rof(n, a, b) for (int n = (a); n > (b); --n)
#define oo 0x3f3f3f3f3f3f3f3f
#define ll long long
#define db long double
#define eps 1e-3
#define bin(x) cerr << bitset<10>(x) << endl
#define what_is(x) cerr << #x << " is " << x << endl
#define met(a, b) memset(a, b, sizeof(a))
#define all(x) x.begin(), x.end()
#define pii pair<ll, ll>
#define pdd pair<db, db>
#define endl "\\n"
const ll mod = 998244353;
const ll maxn = 1e5 + 10;
const db pi = acos(-1.0);

ll qpow(ll a, ll b) {
  ll ret = 1;
  for (; b; a = a * a % mod, b >>= 1) {
    if (b & 1) {
      ret = ret * a % mod;
    }
  }
  return ret;
}

vector<ll> f(maxn), invf(maxn);

ll inv(ll a) { return qpow(a, mod - 2); }

void prework() {
  f[0] = 1;
  _rep(i, 1, maxn - 1) { f[i] = f[i - 1] * i % mod; }
  invf[maxn - 1] = qpow(f[maxn - 1], mod - 2);
  for (ll i = maxn - 2; i >= 0; i--) {
    invf[i] = invf[i + 1] * (i + 1) % mod;
  }
}

ll C(ll n, ll m) {
  if (n > m || m < 0)
    return 0;
  if (n == 0 || m == n)
    return 1;
  ll res = (f[m] * invf[m - n] % mod * invf[n]) % mod;
  return res;
}

int par[maxn];

int find(int a) { return a == par[a] ? a : par[a] = find(a); }

void merge(int a, int b) { par[find(a)] = find(b); }

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  _rep(i, 1, n) { par[i] = i; }
  _rep(i, 1, m) {
    int u, v;
    cin >> u >> v;
    merge(u, v);
  }
  int cnt = 0;
  _rep(i, 1, n) {
    if (par[i] == i) {
      cnt++;
    }
  }
  cout << cnt - 1 << endl;
}

#include <bits/stdc++.h>

using namespace std;
#define local
#ifdef local

template <class T> void _E(T x) { cerr << x; }

void _E(double x) { cerr << fixed << setprecision(6) << x; }

void _E(string s) { cerr << "\\"" << s << "\\""; }

template <class A, class B> void _E(pair<A, B> x) {
  cerr << '(';
  _E(x.first);
  cerr << ", ";
  _E(x.second);
  cerr << ")";
}

template <class T> void _E(vector<T> x) {
  cerr << "[";
  for (auto it = x.begin(); it != x.end(); ++it) {
    if (it != x.begin())
      cerr << ", ";
    _E(*it);
  }
  cerr << "]";
}

void ERR() {}

template <class A, class... B> void ERR(A x, B... y) {
  _E(x);
  cerr << (sizeof...(y) ? ", " : " ");
  ERR(y...);
}

#define debug(x...)                                                            \\
  do {                                                                         \\
    cerr << "{ " #x " } -> { ";                                                \\
    ERR(x);                                                                    \\
    cerr << "}" << endl;                                                       \\
  } while (false)
#else
#define debug(...) 114514.1919810
#endif
#define _rep(n, a, b) for (int n = (a); n <= (b); ++n)
#define _rev(n, a, b) for (int n = (a); n >= (b); --n)
#define _for(n, a, b) for (int n = (a); n < (b); ++n)
#define _rof(n, a, b) for (int n = (a); n > (b); --n)
#define oo 0x3f3f3f3f3f3f3f3f
#define ll long long
#define db long double
#define eps 1e-3
#define bin(x) cerr << bitset<10>(x) << endl
#define what_is(x) cerr << #x << " is " << x << endl
#define met(a, b) memset(a, b, sizeof(a))
#define all(x) x.begin(), x.end()
#define pii pair<ll, ll>
#define pdd pair<db, db>
#define endl "\\n"
const ll mod = 998244353;
const ll maxn = 1e5 + 10;
const db pi = acos(-1.0);

ll qpow(ll a, ll b) {
  ll ret = 1;
  for (; b; a = a * a % mod, b >>= 1) {
    if (b & 1) {
      ret = ret * a % mod;
    }
  }
  return ret;
}

vector<ll> f(maxn), invf(maxn);

ll inv(ll a) { return qpow(a, mod - 2); }

void prework() {
  f[0] = 1;
  _rep(i, 1, maxn - 1) { f[i] = f[i - 1] * i % mod; }
  invf[maxn - 1] = qpow(f[maxn - 1], mod - 2);
  for (ll i = maxn - 2; i >= 0; i--) {
    invf[i] = invf[i + 1] * (i + 1) % mod;
  }
}

ll C(ll n, ll m) {
  if (n > m || m < 0)
    return 0;
  if (n == 0 || m == n)
    return 1;
  ll res = (f[m] * invf[m - n] % mod * invf[n]) % mod;
  return res;
}

int par[maxn];

int find(int a) { return a == par[a] ? a : par[a] = find(par[a]); }

void merge(int a, int b) { par[find(a)] = find(b); }

signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  _rep(i, 1, n) { par[i] = i; }
  _rep(i, 1, m) {
    int u, v;
    cin >> u >> v;
    merge(u, v);
  }
  int cnt = 0;
  _rep(i, 1, n) {
    if (par[i] == i) {
      cnt++;
    }
  }
  cout << cnt - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;
int n, m, a, b, blocks;
vector<int> toc;
vector<vector<int>> rd;
bool used[100009];
void DFS(int i) {
  used[i] = 1;
  for (int j = 0; j < toc[i]; j++) {
    DFS(rd[i][j]);
  }
}
int main() {
  cin >> n >> m;
  rd.resize(n);
  toc.resize(n);
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    rd[a - 1].push_back(b - 1);
    rd[b - 1].push_back(a - 1);
    toc[a - 1]++;
    toc[b - 1]++;
  }
  for (int i = 0; i < n; i++) {
    if (used[i])
      continue;
    blocks++;
    DFS(i);
  }
  cout << blocks - 1;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
int n, m, a, b, blocks;
vector<int> toc;
vector<vector<int>> rd;
bool used[100009];
void DFS(int i) {
  used[i] = 1;
  for (int j = 0; j < toc[i]; j++) {
    if (used[rd[i][j]])
      continue;
    DFS(rd[i][j]);
  }
}
int main() {
  cin >> n >> m;
  rd.resize(n);
  toc.resize(n);
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    rd[a - 1].push_back(b - 1);
    rd[b - 1].push_back(a - 1);
    toc[a - 1]++;
    toc[b - 1]++;
  }
  for (int i = 0; i < n; i++) {
    if (used[i])
      continue;
    blocks++;
    DFS(i);
  }
  cout << blocks - 1;
  return 0;
}


#line 1 "main.cpp"
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC target("avx")
#pragma GCC optimize("O3")
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cfloat>
#include <climits>
#include <cmath>
#include <complex>
#include <ctime>
#include <deque>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#define rep(i, n) for (int i = 0; i < (lint)(n); i++)
#define REP(i, n) for (int i = 1; i <= (lint)(n); i++)
#define all(V) V.begin(), V.end()
#define stackReplaceBegin(size)                                                \\
  void *p = malloc(size);                                                      \\
  esp_new = (lint)p + size - 1;                                                \\
  void *stack_extend_memory_ = malloc(size);                                   \\
  void *stack_extend_origin_memory_;                                           \\
  char *stack_extend_dummy_memory_ = (char *)alloca(                           \\
      (1 + (int)(((long long)stack_extend_memory_) & 127)) * 16);              \\
  *stack_extend_dummy_memory_ = 0;                                             \\
  asm volatile("mov %%rsp, %%rbx\\nmov %%rax, %%rsp"                            \\
               : "=b"(stack_extend_origin_memory_)                             \\
               : "a"((char *)stack_extend_memory_ + (size)-1024));
typedef long long lint;
typedef unsigned long long ulint;
typedef std::pair<int, int> P;
constexpr int INF = INT_MAX / 2;
constexpr lint LINF = LLONG_MAX / 2;
constexpr double eps = DBL_EPSILON;
constexpr double PI = 3.141592653589793238462643383279;
lint esp_new;
template <class T>
class prique : public std::priority_queue<T, std::vector<T>, std::greater<T>> {
};
template <class T, class U> inline bool chmax(T &lhs, const U &rhs) {
  if (lhs < rhs) {
    lhs = rhs;
    return 1;
  }
  return 0;
}
template <class T, class U> inline bool chmin(T &lhs, const U &rhs) {
  if (lhs > rhs) {
    lhs = rhs;
    return 1;
  }
  return 0;
}
inline lint gcd(lint a, lint b) {
  while (b) {
    lint c = a;
    a = b;
    b = c % b;
  }
  return a;
}
inline lint lcm(lint a, lint b) { return a / gcd(a, b) * b; }
bool isprime(lint n) {
  if (n == 1)
    return false;
  for (int i = 2; i * i <= n; i++) {
    if (n % i == 0)
      return false;
  }
  return true;
}
template <typename T> T mypow(T a, unsigned int b) {
  T res(1), now(a);
  while (b) {
    if (b & 1)
      res *= now;
    b >>= 1;
    now *= now;
  }
  return res;
}
lint modpow(lint a, unsigned int b, unsigned int mod) {
  lint res(1), now(a % mod);
  while (b) {
    if (b & 1) {
      res *= now;
      res %= mod;
    }
    b >>= 1;
    now *= now;
    now %= mod;
  }
  return res;
}
template <typename T> void printArray(std::vector<T> &vec) {
  rep(i, (int)vec.size() - 1) std::cout << vec[i] << " ";
  if (!vec.empty())
    std::cout << vec.back() << std::endl;
}
template <typename T> void printArray(T l, T r) {
  T rprev = r;
  rprev--;
  for (T i = l; i != rprev; i++) {
    std::cout << *i << " ";
  }
  std::cout << *rprev << std::endl;
}
int n, m;
std::vector<int> vec[30010];
bool used[30010];
void dfs(int node) {
  used[node] = true;
  for (int i : vec[node]) {
    if (!used[i])
      dfs(i);
  }
}
int main() {
  std::cin >> n >> m;
  rep(i, m) {
    int a, b;
    std::cin >> a >> b;
    vec[a].emplace_back(b);
    vec[b].emplace_back(a);
  }
  int ans = 0;
  REP(i, n) {
    if (!used[i]) {
      dfs(i);
      ans++;
    }
  }
  std::cout << ans - 1 << std::endl;
  return 0;
}

#line 1 "main.cpp"
#define _CRT_SECURE_NO_WARNINGS
#pragma GCC target("avx")
#pragma GCC optimize("O3")
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cfloat>
#include <climits>
#include <cmath>
#include <complex>
#include <ctime>
#include <deque>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#define rep(i, n) for (int i = 0; i < (lint)(n); i++)
#define REP(i, n) for (int i = 1; i <= (lint)(n); i++)
#define all(V) V.begin(), V.end()
#define stackReplaceBegin(size)                                                \\
  void *p = malloc(size);                                                      \\
  esp_new = (lint)p + size - 1;                                                \\
  void *stack_extend_memory_ = malloc(size);                                   \\
  void *stack_extend_origin_memory_;                                           \\
  char *stack_extend_dummy_memory_ = (char *)alloca(                           \\
      (1 + (int)(((long long)stack_extend_memory_) & 127)) * 16);              \\
  *stack_extend_dummy_memory_ = 0;                                             \\
  asm volatile("mov %%rsp, %%rbx\\nmov %%rax, %%rsp"                            \\
               : "=b"(stack_extend_origin_memory_)                             \\
               : "a"((char *)stack_extend_memory_ + (size)-1024));
typedef long long lint;
typedef unsigned long long ulint;
typedef std::pair<int, int> P;
constexpr int INF = INT_MAX / 2;
constexpr lint LINF = LLONG_MAX / 2;
constexpr double eps = DBL_EPSILON;
constexpr double PI = 3.141592653589793238462643383279;
lint esp_new;
template <class T>
class prique : public std::priority_queue<T, std::vector<T>, std::greater<T>> {
};
template <class T, class U> inline bool chmax(T &lhs, const U &rhs) {
  if (lhs < rhs) {
    lhs = rhs;
    return 1;
  }
  return 0;
}
template <class T, class U> inline bool chmin(T &lhs, const U &rhs) {
  if (lhs > rhs) {
    lhs = rhs;
    return 1;
  }
  return 0;
}
inline lint gcd(lint a, lint b) {
  while (b) {
    lint c = a;
    a = b;
    b = c % b;
  }
  return a;
}
inline lint lcm(lint a, lint b) { return a / gcd(a, b) * b; }
bool isprime(lint n) {
  if (n == 1)
    return false;
  for (int i = 2; i * i <= n; i++) {
    if (n % i == 0)
      return false;
  }
  return true;
}
template <typename T> T mypow(T a, unsigned int b) {
  T res(1), now(a);
  while (b) {
    if (b & 1)
      res *= now;
    b >>= 1;
    now *= now;
  }
  return res;
}
lint modpow(lint a, unsigned int b, unsigned int mod) {
  lint res(1), now(a % mod);
  while (b) {
    if (b & 1) {
      res *= now;
      res %= mod;
    }
    b >>= 1;
    now *= now;
    now %= mod;
  }
  return res;
}
template <typename T> void printArray(std::vector<T> &vec) {
  rep(i, (int)vec.size() - 1) std::cout << vec[i] << " ";
  if (!vec.empty())
    std::cout << vec.back() << std::endl;
}
template <typename T> void printArray(T l, T r) {
  T rprev = r;
  rprev--;
  for (T i = l; i != rprev; i++) {
    std::cout << *i << " ";
  }
  std::cout << *rprev << std::endl;
}
int n, m;
std::vector<int> vec[100010];
bool used[100010];
void dfs(int node) {
  used[node] = true;
  for (int i : vec[node]) {
    if (!used[i])
      dfs(i);
  }
}
int main() {
  std::cin >> n >> m;
  rep(i, m) {
    int a, b;
    std::cin >> a >> b;
    vec[a].emplace_back(b);
    vec[b].emplace_back(a);
  }
  int ans = 0;
  REP(i, n) {
    if (!used[i]) {
      dfs(i);
      ans++;
    }
  }
  std::cout << ans - 1 << std::endl;
  return 0;
}


#include <bits/stdc++.h>

using namespace std;

#define G getline
#define SZ size()
#define C clear()
#define B begin()
#define F front()
#define T top()
#define E end()
#define EM empty()
#define V vector
#define Q queue
#define DQ deque
#define PQ priority_queue
#define ST stack
#define FI first
#define SE second
#define PI acos(-1)
#define PS push
#define PP pop()
#define PSF push_front
#define PSB push_back
#define PPF pop_front()
#define PPB pop_back()
#define LL long long
#define ULL unsigned long long
#define PII pair<int, int>
#define PSI pair<string, int>
#define PIS pair<int, string>
#define PLI pair<LL, int>
#define PLL pair<LL, LL>
#define MII map<int, int>
#define MSI map<string, int>
#define MIS map<int, string>
#define MLI map<LL, int>
#define FAST()                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)

// int dx[]={-1,1,0,0};
// int dy[]={0,0,-1,1};
// int dx[]={-1,0,1,-1,1,-1,0,1};
// int dy[]={1,1,1,0,0,-1,-1,-1};

const int N = 1e5 + 5;
vector<int> adj[N];
bool visited[N];

void DFS(int cur, int par) {
  visited[cur] = 1;
  for (int x : adj[cur]) {
    if (x ^ par)
      DFS(x, cur);
  }
}

int main() {
  FAST();
  int i, m, n, x, y, ans = 0;
  cin >> n >> m;
  while (m--) {
    cin >> x >> y;
    adj[x].emplace_back(y);
    adj[y].emplace_back(x);
  }
  for (i = 1; i <= n; i++) {
    if (!visited[i]) {
      ++ans;
      DFS(i, i);
    }
  }
  cout << ans - 1;
  return 0;
}

#include <bits/stdc++.h>

using namespace std;

#define G getline
#define SZ size()
#define C clear()
#define B begin()
#define F front()
#define T top()
#define E end()
#define EM empty()
#define V vector
#define Q queue
#define DQ deque
#define PQ priority_queue
#define ST stack
#define FI first
#define SE second
#define PI acos(-1)
#define PS push
#define PP pop()
#define PSF push_front
#define PSB push_back
#define PPF pop_front()
#define PPB pop_back()
#define LL long long
#define ULL unsigned long long
#define PII pair<int, int>
#define PSI pair<string, int>
#define PIS pair<int, string>
#define PLI pair<LL, int>
#define PLL pair<LL, LL>
#define MII map<int, int>
#define MSI map<string, int>
#define MIS map<int, string>
#define MLI map<LL, int>
#define FAST()                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)

// int dx[]={-1,1,0,0};
// int dy[]={0,0,-1,1};
// int dx[]={-1,0,1,-1,1,-1,0,1};
// int dy[]={1,1,1,0,0,-1,-1,-1};

const int N = 1e5 + 5;
vector<int> adj[N];
bool visited[N];

void DFS(int cur, int par) {
  if (visited[cur])
    return;
  visited[cur] = 1;
  for (int x : adj[cur]) {
    if (x ^ par)
      DFS(x, cur);
  }
}

int main() {
  FAST();
  int i, m, n, x, y, ans = 0;
  cin >> n >> m;
  while (m--) {
    cin >> x >> y;
    adj[x].emplace_back(y);
    adj[y].emplace_back(x);
  }
  for (i = 1; i <= n; i++) {
    if (!visited[i]) {
      ++ans;
      DFS(i, i);
    }
  }
  cout << ans - 1;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
using ll = unsigned long long int;
vector<int> adj[30002];
bool v[30002];
int tam;

void dfs(int i) {
  if (v[i])
    return;
  v[i] = true;
  tam++;
  for (int j = 0; j < adj[i].size(); j++) {
    dfs(adj[i][j]);
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  set<int> node;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
    node.insert(a);
    node.insert(b);
  }
  tam = 0;
  int grafos = 0;
  for (int i = 1; i <= n; i++) {
    if (!v[i] && node.count(i)) {
      dfs(i);
      grafos++;
    }
  }
  // cout<<grafos<<" "<<tam<<endl;
  int ans = 0;
  ans += (grafos - 1);
  ans += (n - tam);
  cout << ans;
}

#include <bits/stdc++.h>
using namespace std;
using ll = unsigned long long int;
vector<int> adj[100002];
bool v[100002];
int tam;

void dfs(int i) {
  if (v[i])
    return;
  v[i] = true;
  tam++;
  for (int j = 0; j < adj[i].size(); j++) {
    dfs(adj[i][j]);
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  set<int> node;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
    node.insert(a);
    node.insert(b);
  }
  tam = 0;
  int grafos = 0;
  for (int i = 1; i <= n; i++) {
    if (!v[i] && node.count(i)) {
      dfs(i);
      grafos++;
    }
  }
  // cout<<grafos<<" "<<tam<<endl;
  int ans = 0;
  ans += (grafos - 1);
  ans += (n - tam);
  cout << ans;
}


// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;
int n, m, k;
int x, y;
int p[5000];
int find(int x) {
  if (p[x] != x)
    p[x] = find(p[x]);
  return p[x];
}
map<int, bool> mp;
int main() {
  cin >> n >> m;
  for (int i = 0; i < n; ++i)
    p[i] = i;
  for (int i = 0; i < m; ++i) {
    cin >> x >> y;
    p[find(x - 1)] = find(y - 1);
  }
  int sum = 0;
  for (int i = 0; i < n; ++i) {
    if (!mp[find(i)]) {
      sum++;
      mp[find(i)] = true;
    }
  }
  cout << sum - 1 << endl;
  return 0;
}

// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;
int n, m, k;
int x, y;
int p[100001];
int find(int x) {
  if (p[x] != x)
    p[x] = find(p[x]);
  return p[x];
}
map<int, bool> mp;
int main() {
  cin >> n >> m;
  for (int i = 0; i < n; ++i)
    p[i] = i;
  for (int i = 0; i < m; ++i) {
    cin >> x >> y;
    p[find(x - 1)] = find(y - 1);
  }
  int sum = 0;
  for (int i = 0; i < n; ++i) {
    if (!mp[find(i)]) {
      sum++;
      mp[find(i)] = true;
    }
  }
  cout << sum - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
#define rep3(i, s, n, a) for (int i = (s); i < (int)(n); i += a)
#define rep2(i, s, n) rep3(i, s, n, 1)
#define rep(i, n) rep2(i, 0, n)

using namespace std;
using ll = long long;
using ull = unsigned long long;
using P = pair<int, int>;

struct UnionFind {
  vector<int> par;

  UnionFind(int n) : par(n, -1) {}
  // assign: 総とっかえ
  void init(int n) { par.assign(n, -1); }

  int root(int x) {
    if (par[x] < 0)
      return x;
    else
      return par[x] = root(par[x]);
  }

  bool issame(int x, int y) { return root(x) == root(y); }

  bool merge(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;

    // xにyをmerge
    if (par[x] > par[y])
      swap(x, y); // merge technique
    par[x] += par[y];
    par[y] = x;
    return true;
  }

  int size(int x) { return -par[root(x)]; }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    uf.merge(a, b);
  }
  set<int> r;
  rep(i, n) { r.insert(uf.root(i + 1)); }
  cout << r.size() - 1 << endl;
  return 0;
}

#include <bits/stdc++.h>
#define rep3(i, s, n, a) for (int i = (s); i < (int)(n); i += a)
#define rep2(i, s, n) rep3(i, s, n, 1)
#define rep(i, n) rep2(i, 0, n)

using namespace std;
using ll = long long;
using ull = unsigned long long;
using P = pair<int, int>;

struct UnionFind {
  vector<int> par;

  UnionFind(int n) : par(n, -1) {}
  // assign: 総とっかえ
  void init(int n) { par.assign(n, -1); }

  int root(int x) {
    if (par[x] < 0)
      return x;
    else
      return par[x] = root(par[x]);
  }

  bool issame(int x, int y) { return root(x) == root(y); }

  bool merge(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;

    // xにyをmerge
    if (par[x] > par[y])
      swap(x, y); // merge technique
    par[x] += par[y];
    par[y] = x;
    return true;
  }

  int size(int x) { return -par[root(x)]; }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    uf.merge(a, b);
  }
  set<int> r;
  rep(i, n) { r.insert(uf.root(i)); }
  cout << r.size() - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
#include <cstdlib>
#include <numeric>
#define ll long long int
#define all(x) x.begin(), x.end()

#define fastio                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)

#define v vector<long long int>
#define vv vector<int>
#define gcd(m, n) __gcd(m, n)
#define vp vector<pair<long long int, long long int>>
#define pb push_back
#define mp make_pair
#define pp pop_back
#define iter vector<int>::iterator
#define pa pair<long long int, long long int>
#define f(i, n) for (ll i = 0; i < n; i++)
#define loop(i, a, n) for (ll i = a; i < n; i += 1)
#define fe(d, e) for (auto d : e)
#define mod 1000000007
#define F first
#define S second
#define sett set<long long int>
#define um unordered_map<ll, ll>
#define N 100005LL
using namespace std;

vector<vector<int>> graph;
vector<bool> visited;

void dfs(int node) {
  visited[node] = true;
  for (auto u : graph[node])
    if (!visited[u])
      dfs(u);
}

int solve1() {
  int n, m;
  cin >> n >> m;
  graph.resize(n + 1);
  visited.resize(n + 1, false);
  f(i, m) {
    ll x, y;
    cin >> x >> y;
    graph[x].pb(y);
    graph[y].pb(x);
  }
  ll ans = 0;
  loop(i, 1, n + 1) {
    if (!visited[i]) {
      ans += 1;
      dfs(i);
    }
  }

  cout << ans - 1 << "\\n";
}

int main() {
  fastio;
  ll t = 1;
  // cin>>t;
  while (t--)
    solve1();
}

#include <bits/stdc++.h>
#include <cstdlib>
#include <numeric>
#define ll long long int
#define all(x) x.begin(), x.end()

#define fastio                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)

#define v vector<long long int>
#define vv vector<int>
#define gcd(m, n) __gcd(m, n)
#define vp vector<pair<long long int, long long int>>
#define pb push_back
#define mp make_pair
#define pp pop_back
#define iter vector<int>::iterator
#define pa pair<long long int, long long int>
#define f(i, n) for (ll i = 0; i < n; i++)
#define loop(i, a, n) for (ll i = a; i < n; i += 1)
#define fe(d, e) for (auto d : e)
#define mod 1000000007
#define F first
#define S second
#define sett set<long long int>
#define um unordered_map<ll, ll>
#define N 100005LL
using namespace std;

vector<vector<int>> graph;
vector<bool> visited;

void dfs(int node) {
  visited[node] = true;
  for (auto u : graph[node])
    if (!visited[u])
      dfs(u);
}

void solve1() {
  int n, m;
  cin >> n >> m;
  graph.resize(n + 1);
  visited.resize(n + 1, false);
  f(i, m) {
    ll x, y;
    cin >> x >> y;
    graph[x].pb(y);
    graph[y].pb(x);
  }
  ll ans = 0;
  loop(i, 1, n + 1) {
    if (!visited[i]) {
      ans += 1;
      dfs(i);
    }
  }

  cout << ans - 1 << "\\n";
}

int main() {
  fastio;
  ll t = 1;
  // cin>>t;
  while (t--)
    solve1();
}


#include <iostream>
#include <vector>

using namespace std;

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2
  int groups;

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
    groups = N;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
    groups--;
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};

int main() {
  int N, M;
  cin >> N >> M;
  UnionFind t(N);
  for (int i = 0; i < M; i++) {
    int a, b;
    cin >> a >> b;
    t.unite(a, b);
  }
  cout << t.groups - 1 << endl;
}

#include <iostream>
#include <vector>

using namespace std;

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2
  int groups;

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
    groups = N;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
    groups--;
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};

int main() {
  int N, M;
  cin >> N >> M;
  UnionFind t(N);
  for (int i = 0; i < M; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    t.unite(a, b);
  }
  cout << t.groups - 1 << endl;
}


#include <algorithm>
#include <cmath>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, n) for (int i = 0; i < (n); ++i)

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind tree(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    tree.unite(a, b);
  }
  int cnt = 0;
  rep(i, n) {
    if (tree.par[i] == i)
      cnt++;
  }
  cout << cnt - 1 << endl;
}

#include <algorithm>
#include <cmath>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, n) for (int i = 0; i < (n); ++i)

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind tree(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    tree.unite(a - 1, b - 1);
  }
  int cnt = 0;
  rep(i, n) {
    if (tree.par[i] == i)
      cnt++;
  }
  cout << cnt - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;

#define ALL(v) v.begin(), v.end()
#define V vector
#define P pair
using ll = long long;

struct UnionFind {
  vector<int> d;
  // n要素で初期化
  UnionFind(int n) : d(n, -1) {}
  // 木の根を決める
  int root(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = root(d[x]);
  }
  // xとy属する集合を併合
  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  // xとyが同じ集合に属するか否か
  bool same(int x, int y) { return root(x) == root(y); }
  // xの属する集合のサイズ
  int size(int x) { return -d[root(x)]; }
};

int main() {
  int n, m;
  cin >> n >> m;

  UnionFind uf(n);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    uf.unite(a, b);
  }

  int ans = 0;
  for (int i = 0; i < n; i++) {
    if (!uf.same(i, i + 1)) {
      uf.unite(i, i + 1);
      ans++;
    }
  }
  cout << ans << endl;

  return 0;
}

#include <bits/stdc++.h>
using namespace std;

#define ALL(v) v.begin(), v.end()
#define V vector
#define P pair
using ll = long long;

struct UnionFind {
  vector<int> d;
  // n要素で初期化
  UnionFind(int n) : d(n, -1) {}
  // 木の根を決める
  int root(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = root(d[x]);
  }
  // xとy属する集合を併合
  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  // xとyが同じ集合に属するか否か
  bool same(int x, int y) { return root(x) == root(y); }
  // xの属する集合のサイズ
  int size(int x) { return -d[root(x)]; }
};

int main() {
  int n, m;
  cin >> n >> m;

  UnionFind uf(n);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    uf.unite(a, b);
  }

  int ans = 0;
  for (int i = 0; i < n - 1; i++) {
    if (!uf.same(i, i + 1)) {
      uf.unite(i, i + 1);
      ans++;
    }
  }
  cout << ans << endl;

  return 0;
}


#include <bits/stdc++.h>
#pragma GCC optimize("unroll-loops,no-stack-protector")
#pragma GCC target("sse,sse2,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#define watch(x) cout << (#x) << " is " << (x) << endl
#define debug cout << "hi" << endl

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;

ll gcd(ll a, ll b) { return (!b ? a : gcd(b, a % b)); }
ll lcm(ll a, ll b) { return ((a * b) / gcd(a, b)); }
bool cmp(int a, int b) { return a > b; }

const ll mod = 1e9 + 7;
const ll MOD = 998244353;
const int INF32 = 1 << 30;
const ll INF64 = 1LL << 60;
const ld pi = 3.141592653589793;

long long modpow(long long n, long long k, long long mod) {
  if (k == 0)
    return 1;
  long long r = modpow(n * n % mod, k >> 1, mod);
  if (k & 1)
    r = r * n % mod;
  return r;
}
vector<vector<int>> adj(1e5 + 10);
vector<bool> b(1e5 + 10, 0);
void dfs(int v, int prev) {
  b[v] = 1;
  for (int i = 0; i < adj[v].size(); i++) {
    int vi = adj[v][i];
    if (vi == prev)
      continue;
    dfs(vi, v);
  }
}
void solve() {
  int n, m;
  cin >> n >> m;
  int ans = -1;
  for (int i = 0; i < m; i++) {
    int v, u;
    cin >> v >> u;
    v--;
    u--;
    adj[v].push_back(u);
    adj[u].push_back(v);
  }
  for (int i = 0; i < n; i++) {
    if (!b[i]) {
      dfs(i, -1);
      ans++;
    }
  }
  cout << ans;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  solve();
  return 0;
}

#include <bits/stdc++.h>
#pragma GCC optimize("unroll-loops,no-stack-protector")
#pragma GCC target("sse,sse2,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#define watch(x) cout << (#x) << " is " << (x) << endl
#define debug cout << "hi" << endl

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;

ll gcd(ll a, ll b) { return (!b ? a : gcd(b, a % b)); }
ll lcm(ll a, ll b) { return ((a * b) / gcd(a, b)); }
bool cmp(int a, int b) { return a > b; }

const ll mod = 1e9 + 7;
const ll MOD = 998244353;
const int INF32 = 1 << 30;
const ll INF64 = 1LL << 60;
const ld pi = 3.141592653589793;

long long modpow(long long n, long long k, long long mod) {
  if (k == 0)
    return 1;
  long long r = modpow(n * n % mod, k >> 1, mod);
  if (k & 1)
    r = r * n % mod;
  return r;
}
vector<vector<int>> adj(1e5 + 10);
vector<bool> b(1e5 + 10, 0);
void dfs(int v) {
  b[v] = 1;
  for (int i = 0; i < adj[v].size(); i++) {
    int vi = adj[v][i];
    if (!b[vi])
      dfs(vi);
  }
}
void solve() {
  int n, m;
  cin >> n >> m;
  int ans = -1;
  for (int i = 0; i < m; i++) {
    int v, u;
    cin >> v >> u;
    v--;
    u--;
    adj[v].push_back(u);
    adj[u].push_back(v);
  }
  for (int i = 0; i < n; i++) {
    if (!b[i]) {
      dfs(i);
      ans++;
    }
  }
  cout << ans;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  solve();
  return 0;
}


#include "algorithm"
#include "bitset"
#include "cassert"
#include "climits"
#include "cmath"
#include "cstdio"
#include "ctime"
#include "functional"
#include "iomanip"
#include "iostream"
#include "list"
#include "map"
#include "numeric"
#include "queue"
#include "random"
#include "set"
#include "stack"
#include "string"
#include "unordered_map"
#include "unordered_set"

using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<ll, ll> Pll;
#define rep(begin, i, end) for (ll i = begin; i < (ll)(end); i++)
#define all(v) v.begin(), v.end()
const int MOD = 1000000007;

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};

int main() {
  int N, M;
  cin >> N >> M;
  UnionFind tree(N);

  rep(0, i, M) {
    int a, b;
    cin >> a >> b;
    tree.unite(a, b);
  }

  map<int, int> m;
  rep(0, i, N) { m[tree.root(i)]++; }

  ll ans = m.size() - 1;

  cout << ans << endl;
}

#include "algorithm"
#include "bitset"
#include "cassert"
#include "climits"
#include "cmath"
#include "cstdio"
#include "ctime"
#include "functional"
#include "iomanip"
#include "iostream"
#include "list"
#include "map"
#include "numeric"
#include "queue"
#include "random"
#include "set"
#include "stack"
#include "string"
#include "unordered_map"
#include "unordered_set"

using namespace std;

typedef long long ll;
typedef pair<int, int> P;
typedef pair<ll, ll> Pll;
#define rep(begin, i, end) for (ll i = begin; i < (ll)(end); i++)
#define all(v) v.begin(), v.end()
const int MOD = 1000000007;

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};

int main() {
  int N, M;
  cin >> N >> M;
  UnionFind tree(N);

  rep(0, i, M) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    tree.unite(a, b);
  }

  map<int, int> m;
  rep(0, i, N) { m[tree.root(i)]++; }

  ll ans = m.size() - 1;

  cout << ans << endl;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n;
vector<vector<int>> g(n);
vector<bool> vis(n, false);

void dfs(int u) {

  vis[u] = true;
  for (auto v : g[u]) {
    if (vis[v])
      continue;

    vis[v] = true;
    dfs(v);
  }
}

int main() {

  int count = 0;
  int n, m;
  cin >> n >> m;

  for (int i = 0; i < m; i++) {

    int a, b;
    cin >> a >> b;
    g[a].push_back(b);
    g[b].push_back(a);
  }

  for (int i = 0; i < n; i++) {

    if (vis[i] == true)
      continue;

    count++;
    dfs(i);
  }
  cout << count - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;

void dfs(int u, vector<vector<int>> &g, vector<bool> &vis) {

  vis[u] = 1;
  for (auto v : g[u]) {

    if (vis[v] == 0) {
      dfs(v, g, vis);
    }
  }
}

int main() {

  int count = 0;
  int n, m;
  cin >> n >> m;

  vector<vector<int>> g(n + 1);
  vector<bool> vis(n + 1, false);

  for (int i = 0; i < m; i++) {

    int a, b;
    cin >> a >> b;
    g[a].push_back(b);
    g[b].push_back(a);
  }

  for (int i = 1; i <= n; i++) {

    if (vis[i] == false) {

      dfs(i, g, vis);
      count++;
    }
  }
  cout << count - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 999999999
#define rep(i, n) for (int i = 0; i < n; i++)
const ll MOD = 1000000007;
ll cnt = 0, ans = 0;
const int MAX = 510000;

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};
int main() {

  ll n, m;
  cin >> n >> m;
  UnionFind tree(n);
  vector<int> a(m);
  vector<int> b(m);
  for (int i = 0; i < m; i++) {
    cin >> a.at(i) >> b.at(i);
    int c, d;
    c = a.at(i);
    d = b.at(i);
    tree.unite(c, d);
  }

  map<int, int> mp;
  for (int i = 1; i <= n; i++) {
    mp[tree.root(i)]++; // 根を数える
  }

  for (int i = 1; i <= n; i++) {
    if (mp[i] != 0)
      ans++;
  }
  cout << ans << endl;
}

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 999999999
#define rep(i, n) for (int i = 0; i < n; i++)
const ll MOD = 1000000007;
ll cnt = 0, ans = 0;
const int MAX = 510000;

struct UnionFind {
  vector<int> par; // par[i]:iの親の番号　(例) par[3] = 2 : 3の親が2

  UnionFind(int N) : par(N) { // 最初は全てが根であるとして初期化
    for (int i = 0; i < N; i++)
      par[i] = i;
  }

  int root(int x) { // データxが属する木の根を再帰で得る：root(x) = {xの木の根}
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(int x, int y) { // xとyの木を併合
    int rx = root(x);        // xの根をrx
    int ry = root(y);        // yの根をry
    if (rx == ry)
      return; // xとyの根が同じ(=同じ木にある)時はそのまま
    par[rx] =
        ry; // xとyの根が同じでない(=同じ木にない)時：xの根rxをyの根ryにつける
  }

  bool same(int x, int y) { // 2つのデータx, yが属する木が同じならtrueを返す
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
};
int main() {

  ll n, m;
  cin >> n >> m;
  UnionFind tree(n + 1);

  vector<int> a(m);
  vector<int> b(m);
  for (int i = 0; i < m; i++) {
    cin >> a.at(i) >> b.at(i);
    int c, d;
    c = a.at(i);
    d = b.at(i);
    tree.unite(c, d);
  }

  map<int, int> mp;
  for (int i = 1; i <= n; i++) {
    mp[tree.root(i)]++; // 根を数える
  }

  for (int i = 0; i <= n; i++) {

    if (mp[i] != 0)
      ans++;
  }
  if (ans == 1) {
    cout << 0 << endl;
    return 0;
  }
  cout << ans - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define rt return 0
#define endln "\\n"
#define all(v) v.begin(), v.end()
#define for1(i, a, b) for (long long int i = a; i < b; i++)
#define for2(i, a, b) for (long long int i = a; i > b; i--)
//  cout << fixed << setprecision(0) << pi <<" "<<npi<<endl;typedef long long
//  int i;
typedef long long int lli;
typedef pair<lli, lli> pll;
int v, E, x = 0;
vector<vector<int>> g; // adjacency list
vector<int> vis;       // stores visit status
int dfs(int cur) {
  int p = cur;
  for (int j = 0; j < g[cur].size(); j++) {
    int k = g[cur][j];
    if (vis[k] == 0) {
      vis[k] = 1;
      dfs(k);
    }
  }
  // cout << cur << " ";
  return 0;
}
int call_dfs() {
  for (int i = 1; i <= v; i++) {
    if (vis[i] == 0) {
      vis[i] = 1;
      dfs(i);
      x += 1;
    }
  }
}
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  // lli n, m;
  cin >> v >> E;
  g.resize(v + 1);
  vis.resize(v + 1);
  for (int i = 1; i <= v; i++) {
    vis[i] = 0;
  }
  for (int i = 0; i < E; i++) {
    int v1, v2;
    cin >> v1 >> v2;
    g[v1].push_back(v2);
    g[v2].push_back(v1);
  }
  call_dfs();
  cout << x - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define rt return 0
#define endln "\\n"
#define all(v) v.begin(), v.end()
#define for1(i, a, b) for (long long int i = a; i < b; i++)
#define for2(i, a, b) for (long long int i = a; i > b; i--)
//  cout << fixed << setprecision(0) << pi <<" "<<npi<<endl;typedef long long
//  int i;
typedef long long int lli;
typedef pair<lli, lli> pll;
int v, E;
vector<vector<int>> g; // adjacency list
vector<int> vis;       // stores visit status
void dfs(int cur) {
  // int p = cur;
  for (int j = 0; j < g[cur].size(); j++) {
    int k = g[cur][j];
    if (vis[k] == 0) {
      vis[k] = 1;
      dfs(k);
    }
  }
  // cout << cur << " ";
  // return 0;
}
int call_dfs() {
  int x = 0;
  for (int i = 1; i <= v; i++) {
    if (vis[i] == 0) {
      vis[i] = 1;
      dfs(i);
      x += 1;
    }
  }
  return x - 1;
}
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  // lli n, m;
  cin >> v >> E;
  g.resize(v + 1);
  vis.resize(v + 1);
  for (int i = 1; i <= v; i++) {
    vis[i] = 0;
  }
  for (int i = 0; i < E; i++) {
    int v1, v2;
    cin >> v1 >> v2;
    g[v1].push_back(v2);
    g[v2].push_back(v1);
  }
  cout << call_dfs() << endl;
}


#include <bits/stdc++.h>
#include <cstdio>
#include <tuple>
#define MAX 1000007
#define MP make_pair
#define ii long long
#define dd double
#define MAX_SIZE 1e7
#define mod 1000000007
#define gc getchar_unlocked
#define Fo(i, k, n) for (i = k; k < n ? i < n : i > n; k < n ? i += 1 : i -= 1)
#define ll long long
#define si(x) scanf("%d", &x)
#define sl(x) scanf("%lld", &x)
#define ss(s) scanf("%s", s)
#define pi(x) printf("%d\\n", x)
#define pl(x) printf("%lld\\n", x)
#define ps(s) printf("%s\\n", s)
#define deb(x) cout << #x << "=" << x << endl
#define deb2(x, y) cout << #x << "=" << x << "," << #y << "=" << y << endl
#define pb push_back
#define mp make_pair
#define F first
#define S second
#define all(x) x.begin(), x.end()
#define clr(x) memset(x, 0, sizeof(x))
#define sortall(x) sort(all(x))
#define tr(it, a) for (auto it = a.begin(); it != a.end(); it++)
#define PI 3.1415926535897932384626
#define REP(i, b, n) for (long long i = b; i < n; i++)
#define rep(i, n) REP(i, 0, n)
#define sz(a) int((a).size())
#define forn(i, n)                                                             \\
  for (int i = 0; i < int(n); ++i)                                             \\
  ..> ||
#define w(x)                                                                   \\
  int x;                                                                       \\
  cin >> x;                                                                    \\
  while (x--)
#define inp(tmp)                                                               \\
  ii tmp;                                                                      \\
  cin >> tmp
#define DBG 0
#define print if (DBG)
using namespace std;
typedef pair<ii, ii> pii;
typedef pair<ll, ll> pl;
typedef vector<ii> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pl> vpl;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef pair<int, vi> Node;

ii n, m;
vi par;
ii find_set(ii v) {
  if (v == par[v])
    return v;
  return find_set(par[v]);
}

void con(ii a, ii b) {
  a = find_set(a);
  b = find_set(b);
  if (a != b)
    par[b] = a;
}
int main() {
  cin >> n >> m;
  par.pb(-1);
  REP(i, 1, n + 1) { par.pb(i); }

  rep(i, m) {
    inp(x);
    inp(y);
    con(x, y);
  }
  ii comp = 0;
  REP(i, 1, n + 1) {
    if (par[i] == i) {
      print cout << i << " is main ";
      comp++;
    }
  }
  cout << comp - 1;
  cin >> n;
}
/*
3 1
1 2

*/

#include <bits/stdc++.h>
#include <cstdio>
#include <tuple>
#define MAX 1000007
#define MP make_pair
#define ii long long
#define dd double
#define MAX_SIZE 1e7
#define mod 1000000007
#define gc getchar_unlocked
#define Fo(i, k, n) for (i = k; k < n ? i < n : i > n; k < n ? i += 1 : i -= 1)
#define ll long long
#define si(x) scanf("%d", &x)
#define sl(x) scanf("%lld", &x)
#define ss(s) scanf("%s", s)
#define pi(x) printf("%d\\n", x)
#define pl(x) printf("%lld\\n", x)
#define ps(s) printf("%s\\n", s)
#define deb(x) cout << #x << "=" << x << endl
#define deb2(x, y) cout << #x << "=" << x << "," << #y << "=" << y << endl
#define pb push_back
#define mp make_pair
#define F first
#define S second
#define all(x) x.begin(), x.end()
#define clr(x) memset(x, 0, sizeof(x))
#define sortall(x) sort(all(x))
#define tr(it, a) for (auto it = a.begin(); it != a.end(); it++)
#define PI 3.1415926535897932384626
#define REP(i, b, n) for (long long i = b; i < n; i++)
#define rep(i, n) REP(i, 0, n)
#define sz(a) int((a).size())
#define forn(i, n)                                                             \\
  for (int i = 0; i < int(n); ++i)                                             \\
  ..> ||
#define w(x)                                                                   \\
  int x;                                                                       \\
  cin >> x;                                                                    \\
  while (x--)
#define inp(tmp)                                                               \\
  ii tmp;                                                                      \\
  cin >> tmp
#define DBG 0
#define print if (DBG)
using namespace std;
typedef pair<ii, ii> pii;
typedef pair<ll, ll> pl;
typedef vector<ii> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pl> vpl;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef pair<int, vi> Node;

ii n, m;
vi par;
ii find_set(ii v) {
  if (v == par[v])
    return v;
  return par[v] = find_set(par[v]);
}

void con(ii a, ii b) {
  a = find_set(a);
  b = find_set(b);
  if (a != b)
    par[b] = a;
}
int main() {
  cin >> n >> m;
  par.pb(-1);
  REP(i, 1, n + 1) { par.pb(i); }

  rep(i, m) {
    inp(x);
    inp(y);
    con(x, y);
  }
  ii comp = 0;
  REP(i, 1, n + 1) {
    if (par[i] == i) {
      print cout << i << " is main ";
      comp++;
    }
  }
  cout << comp - 1;
  cin >> n;
}
/*
3 1
1 2

*/


// #include "bits/stdc++.h"
#include <bits/stdc++.h>
using namespace std;
// using namespace atcoder;
using ll = long long;

template <typename T>
void print_vec(const vector<T> &v, bool is_reverse = false, ll num = 0) {
  if (num == 0)
    num = (ll)v.size();
  cout << endl;
  cout << "i=  ";
  for (ll i = 0; i < num; i++)
    cout << i << "   ";
  cout << endl;
  cout << "    ";
  if (is_reverse)
    for (ll i = num - 1; i >= 0; i--) {
      cout << v[i];
      if (i != 0)
        cout << "   ";
    }
  else
    for (ll i = 0; i < num; i++) {
      cout << v[i];
      if (i != num - 1)
        cout << "   ";
    }
  cout << endl;
}

// template <typename T>
// void print_pairvec(const vector<T> &v, ll num=0){
//   if(num == 0) num = (ll)v.size();
//   cout << endl; for(ll i=0; i<num; i++){ cout << v[i].first << " " <<
//   v[i].second << endl;}
// }
template <typename T> void print_pairvec(const T &_pair, ll num = 0) {
  cout << endl;
  for (pair<ll, int> x : _pair) {
    cout << x.first << " " << x.second << endl;
  }
}

template <typename T> void print_vec2(const vector<vector<T>> &v) {
  cout << endl;
  cout << "       ";
  for (ll i = 0; i < v[0].size(); i++)
    cout << i << "   ";
  cout << endl;
  for (ll i = 0; i < v.size(); i++) {
    cout << "i=" << i << ":   ";
    for (ll j = 0; j < v[i].size(); j++) {
      if (v[i][j] == 0)
        cout << "\\x1B[0m" << v[i][j] << "   ";
      else
        cout << "\\x1B[31m" << v[i][j]
             << "   "; // https://stackoverrun.com/ja/q/12618775
    }
    cout << "\\x1B[0m" << endl;
  }
}

// UnionFind
// coding: https://youtu.be/TdR816rqc3s?t=726
// comment: https://youtu.be/TdR816rqc3s?t=6822
struct UnionFind {
  vector<int> d;
  UnionFind(int n = 0) : d(n, -1) {}
  int find(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }
  bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  bool same(int x, int y) { return find(x) == find(y); }
  int size(int x) { return -d[find(x)]; }
};
int main() {
  ll n, m;
  cin >> n >> m;

  vector<ll> a(m), b(m);
  for (int i = 0; i < m; i++)
    cin >> a[i] >> b[i];

  UnionFind uf(n);
  for (int i = 0; i < m; i++) {
    uf.unite(a[i], b[i]);
  }

  // vector<bool> is_root(n, false);
  // for(int i=0; i<n; i++){
  //   ll x = uf.find(i);
  //   if(x == -1) is_root[i] = true;
  //   else is_root[x] = true;
  // }

  // ll cnt = 0;
  // for(int i=0; i<n; i++) if(is_root[i]) cnt++;

  ll cnt = 0;
  for (int i = 0; i < n; i++)
    if (uf.d[i] < 0)
      cnt++;
  ll ans = cnt - 1;
  cout << ans << endl;

  return 0;
  // cout << fixed;//小数点を確実に10桁で表示するためのもの
  // cout << setprecision(10);
}

// #include "bits/stdc++.h"
#include <bits/stdc++.h>
using namespace std;
// using namespace atcoder;
using ll = long long;

template <typename T>
void print_vec(const vector<T> &v, bool is_reverse = false, ll num = 0) {
  if (num == 0)
    num = (ll)v.size();
  cout << endl;
  cout << "i=  ";
  for (ll i = 0; i < num; i++)
    cout << i << "   ";
  cout << endl;
  cout << "    ";
  if (is_reverse)
    for (ll i = num - 1; i >= 0; i--) {
      cout << v[i];
      if (i != 0)
        cout << "   ";
    }
  else
    for (ll i = 0; i < num; i++) {
      cout << v[i];
      if (i != num - 1)
        cout << "   ";
    }
  cout << endl;
}

// template <typename T>
// void print_pairvec(const vector<T> &v, ll num=0){
//   if(num == 0) num = (ll)v.size();
//   cout << endl; for(ll i=0; i<num; i++){ cout << v[i].first << " " <<
//   v[i].second << endl;}
// }
template <typename T> void print_pairvec(const T &_pair, ll num = 0) {
  cout << endl;
  for (pair<ll, int> x : _pair) {
    cout << x.first << " " << x.second << endl;
  }
}

template <typename T> void print_vec2(const vector<vector<T>> &v) {
  cout << endl;
  cout << "       ";
  for (ll i = 0; i < v[0].size(); i++)
    cout << i << "   ";
  cout << endl;
  for (ll i = 0; i < v.size(); i++) {
    cout << "i=" << i << ":   ";
    for (ll j = 0; j < v[i].size(); j++) {
      if (v[i][j] == 0)
        cout << "\\x1B[0m" << v[i][j] << "   ";
      else
        cout << "\\x1B[31m" << v[i][j]
             << "   "; // https://stackoverrun.com/ja/q/12618775
    }
    cout << "\\x1B[0m" << endl;
  }
}

// UnionFind
// coding: https://youtu.be/TdR816rqc3s?t=726
// comment: https://youtu.be/TdR816rqc3s?t=6822
struct UnionFind {
  vector<int> d;
  UnionFind(int n = 0) : d(n, -1) {}
  int find(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }
  bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  bool same(int x, int y) { return find(x) == find(y); }
  int size(int x) { return -d[find(x)]; }
};
int main() {
  ll n, m;
  cin >> n >> m;

  vector<ll> a(m), b(m);
  for (int i = 0; i < m; i++)
    cin >> a[i] >> b[i];

  UnionFind uf(n);
  for (int i = 0; i < m; i++) {
    uf.unite(a[i] - 1, b[i] - 1);
  }

  // vector<bool> is_root(n, false);
  // for(int i=0; i<n; i++){
  //   ll x = uf.find(i);
  //   if(x == -1) is_root[i] = true;
  //   else is_root[x] = true;
  // }

  // ll cnt = 0;
  // for(int i=0; i<n; i++) if(is_root[i]) cnt++;

  ll cnt = 0;
  for (int i = 0; i < n; i++)
    if (uf.d[i] < 0)
      cnt++;
  ll ans = cnt - 1;
  cout << ans << endl;

  return 0;
  // cout << fixed;//小数点を確実に10桁で表示するためのもの
  // cout << setprecision(10);
}


#include <iostream>
#include <set>
#include <stack>
#include <vector>
using namespace std;
#define N 100000

int main(void) {
  int n, m, neigh, ver;
  cin >> n >> m;
  vector<int> G[N];
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    G[a].push_back(b);
    G[b].push_back(a);
  }

  vector<int> visit(n, 0);
  int chain[N];
  stack<int> S;
  for (int i = 1; i <= n; i++) {
    if (visit[i] == 0) {
      S.push(i);
      while (!S.empty()) {
        ver = S.top();
        S.pop();
        visit[ver] = 1;
        chain[ver] = i;
        for (int j = 0; j < (int)G[ver].size(); j++) {
          neigh = G[ver][j];
          if (visit[neigh] == 0)
            S.push(neigh);
        }
      }
    }
  }
  set<int> ans;
  for (int i = 1; i <= n; i++) {
    ans.insert(chain[i]);
  }
  cout << ans.size() - 1 << endl;
  return 0;
}

#include <atcoder/dsu>
#include <cstdio>
#include <iostream>
#include <set>
#include <stack>
#include <vector>
using namespace std;
using namespace atcoder;
#define N 100000

int main(void) {
  int n, m;
  cin >> n >> m;
  dsu g(n + 1);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    g.merge(a, b);
  }
  set<int> ans;
  for (int i = 1; i <= n; i++) {
    int k = g.leader(i);
    ans.insert(k);
  }
  cout << ans.size() - 1 << endl;
  return 0;
}