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

#if __has_include(<local.h>)
// #include <local.h>
#endif

using namespace std;

typedef long long int LL;
typedef long long int LLint;

struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

// Faster Program
#define endl "\\n"

// Faster Coding
#define ED return
#define GET(a)                                                                 \\
  int a;                                                                       \\
  cin >> a
#define DGET(a)                                                                \\
  double a;                                                                    \\
  cin >> a
#define SGET(a)                                                                \\
  string a;                                                                    \\
  cin >> a
#define CGET(a)                                                                \\
  char a;                                                                      \\
  cin >> a
#define PRINT(a) cout << a << "\\n"
#define ANS(a, b)                                                              \\
  int a;                                                                       \\
  cin >> a;                                                                    \\
  cout << b << "\\n"
#define REP(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define SORT(vec) sort(vec.begin(), vec.end())
#define REV(vec) reverse(vec.begin(), vec.end())
#define ALL(x) (x).begin(), (x).end()

// As Function
#define UP(a, b) ((a + (b - 1)) / (b))
#define IPOW(x, y) LL(pow(x, y))

// Debug
#ifdef LOCAL_H
#define TEST cout << "OK\\n"
#define DEBUG(a) a
#else
#define TEST
#define DEBUG(a)
#endif

#ifdef LOCAL_H
[[maybe_unused]]
#endif
const double PI = M_PI;
#ifdef LOCAL_H
[[maybe_unused]]
#endif
const int INF = int(999999999);
#ifdef LOCAL_H
[[maybe_unused]]
#endif
const LL LL_INF = LL(999999999999999999);
#ifdef LOCAL_H
[[maybe_unused]]
#endif
const long long MOD = int(1000000007);

// Settings
#ifdef LOCAL_H
const bool DO_BENCHMARK = true;
#endif
const int DOUBLE_ACCURACY = 15;
const int RUN_SOLVE = 1;

#ifndef LOCAL_H
const bool DO_BENCHMARK = false;
#endif

int main() {
  cin.tie(nullptr);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(DOUBLE_ACCURACY);
  for (int do_solve_time = 0; do_solve_time < RUN_SOLVE; do_solve_time++) {
    void solve();
    if (DO_BENCHMARK) {
      clock_t clock_start = clock();
      solve();
      clock_t clock_end = clock();
      const double time = static_cast<double>(clock_end - clock_start) /
                          CLOCKS_PER_SEC * 1000.0;
      cout << "\\n------------------------------------\\n" << time << "[ms]\\n";
    } else
      solve();
  }
}

/*------------------------------↓ここからコード↓----------------------------------*/
void solve() {
  int N, M;
  cin >> N >> M;
  dsu d(N);
  for (int i = 0; i < M; i++) {
    int a, b;
    cin >> a >> b;
    d.merge(a - 1, b - 1);
  }
  int ans = 0;
  for (int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
      if (!d.same(i, j)) {
        ans++;
        d.merge(i, j);
      }
    }
  }
  cout << ans << endl;
  return;
}

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

#if __has_include(<local.h>)
// #include <local.h>
#endif

using namespace std;

typedef long long int LL;
typedef long long int LLint;

struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

// Faster Program
#define endl "\\n"

// Faster Coding
#define ED return
#define GET(a)                                                                 \\
  int a;                                                                       \\
  cin >> a
#define DGET(a)                                                                \\
  double a;                                                                    \\
  cin >> a
#define SGET(a)                                                                \\
  string a;                                                                    \\
  cin >> a
#define CGET(a)                                                                \\
  char a;                                                                      \\
  cin >> a
#define PRINT(a) cout << a << "\\n"
#define ANS(a, b)                                                              \\
  int a;                                                                       \\
  cin >> a;                                                                    \\
  cout << b << "\\n"
#define REP(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define SORT(vec) sort(vec.begin(), vec.end())
#define REV(vec) reverse(vec.begin(), vec.end())
#define ALL(x) (x).begin(), (x).end()

// As Function
#define UP(a, b) ((a + (b - 1)) / (b))
#define IPOW(x, y) LL(pow(x, y))

// Debug
#ifdef LOCAL_H
#define TEST cout << "OK\\n"
#define DEBUG(a) a
#else
#define TEST
#define DEBUG(a)
#endif

#ifdef LOCAL_H
[[maybe_unused]]
#endif
const double PI = M_PI;
#ifdef LOCAL_H
[[maybe_unused]]
#endif
const int INF = int(999999999);
#ifdef LOCAL_H
[[maybe_unused]]
#endif
const LL LL_INF = LL(999999999999999999);
#ifdef LOCAL_H
[[maybe_unused]]
#endif
const long long MOD = int(1000000007);

// Settings
#ifdef LOCAL_H
const bool DO_BENCHMARK = true;
#endif
const int DOUBLE_ACCURACY = 15;
const int RUN_SOLVE = 1;

#ifndef LOCAL_H
const bool DO_BENCHMARK = false;
#endif

int main() {
  cin.tie(nullptr);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(DOUBLE_ACCURACY);
  for (int do_solve_time = 0; do_solve_time < RUN_SOLVE; do_solve_time++) {
    void solve();
    if (DO_BENCHMARK) {
      clock_t clock_start = clock();
      solve();
      clock_t clock_end = clock();
      const double time = static_cast<double>(clock_end - clock_start) /
                          CLOCKS_PER_SEC * 1000.0;
      cout << "\\n------------------------------------\\n" << time << "[ms]\\n";
    } else
      solve();
  }
}

/*------------------------------↓ここからコード↓----------------------------------*/
void solve() {
  int N, M;
  cin >> N >> M;
  dsu d(N);
  for (int i = 0; i < M; i++) {
    int a, b;
    cin >> a >> b;
    d.merge(a - 1, b - 1);
  }
  vector<int> group(N);
  for (int i = 0; i < N; i++) {
    group[i] = d.size(i);
  }
  SORT(group);
  int ans = -1;
  for (int i = 0; i < N;) {
    ans++;
    i += group[i];
  }
  cout << ans << endl;
  return;
}


#include <iostream>
#include <set>
#include <string>
#include <vector>
using namespace std;

#define debug(var)                                                             \\
  do {                                                                         \\
    std::cout << #var << " : ";                                                \\
    view(var);                                                                 \\
  } while (0)
using ll = long long;
using ld = long double;
inline int num(const std::string &S, int idx) noexcept {
  return S.at(idx) - '0';
}
inline int ctoi(char c) noexcept { return c - '0'; }
inline bool isNumber(char c) noexcept {
  return ('0' <= c && c <= '9' ? true : false);
}
inline int safeCtoi(char c) {
  if (isNumber(c)) {
    return ctoi(c);
  } else {
    throw 1;
  }
}
inline char itoc(int i) noexcept { return i + '0'; }
inline unsigned int bitMask(int i) noexcept { return (1 << i); }
inline bool isTrue(unsigned int bit, int i) noexcept {
  return bit & bitMask(i);
}
inline bool isEven(int num) noexcept { return !num % 2; }
inline bool isOdd(int num) noexcept { return num % 2; }
template <typename T> inline short sgn(T num) noexcept {
  return (num == 0 ? 0 : (num > 0 ? 1 : -1));
}
template <typename T> inline bool isZero(T num) noexcept {
  return (num == 0 ? true : false);
}
template <typename T> inline bool isPositive(T num) noexcept {
  return (num > 0 ? true : false);
}
template <typename T> inline bool isNegative(T num) noexcept {
  return (num < 0 ? true : false);
}
template <typename T> void view(T e) noexcept { cout << e; }
template <typename T> void view(std::vector<T> v) noexcept {
  for (const auto &e : v)
    cout << e << " ";
}
template <typename T> void view(std::vector<std::vector<T>> vv) noexcept {
  for (const auto &v : vv)
    view(v);
}

struct UnionFind {
  vector<int> par;

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

  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;
  }

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

int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  for (int i = 0; i < m; ++i) {
    int a, b;
    cin >> a >> b;
    uf.unite(a, b);
  }
  std::set<int> roots;
  for (int i = 1; i < n; ++i) {
    roots.insert(uf.root(i));
  }
  cout << roots.size() << endl;
  return 0;
}

#include <iostream>
#include <set>
#include <string>
#include <vector>
using namespace std;

#define debug(var)                                                             \\
  do {                                                                         \\
    std::cout << #var << " : ";                                                \\
    view(var);                                                                 \\
  } while (0)
using ll = long long;
using ld = long double;
inline int num(const std::string &S, int idx) noexcept {
  return S.at(idx) - '0';
}
inline int ctoi(char c) noexcept { return c - '0'; }
inline bool isNumber(char c) noexcept {
  return ('0' <= c && c <= '9' ? true : false);
}
inline int safeCtoi(char c) {
  if (isNumber(c)) {
    return ctoi(c);
  } else {
    throw 1;
  }
}
inline char itoc(int i) noexcept { return i + '0'; }
inline unsigned int bitMask(int i) noexcept { return (1 << i); }
inline bool isTrue(unsigned int bit, int i) noexcept {
  return bit & bitMask(i);
}
inline bool isEven(int num) noexcept { return !num % 2; }
inline bool isOdd(int num) noexcept { return num % 2; }
template <typename T> inline short sgn(T num) noexcept {
  return (num == 0 ? 0 : (num > 0 ? 1 : -1));
}
template <typename T> inline bool isZero(T num) noexcept {
  return (num == 0 ? true : false);
}
template <typename T> inline bool isPositive(T num) noexcept {
  return (num > 0 ? true : false);
}
template <typename T> inline bool isNegative(T num) noexcept {
  return (num < 0 ? true : false);
}
template <typename T> void view(T e) noexcept { cout << e; }
template <typename T> void view(std::vector<T> v) noexcept {
  for (const auto &e : v)
    cout << e << " ";
}
template <typename T> void view(std::vector<std::vector<T>> vv) noexcept {
  for (const auto &v : vv)
    view(v);
}

struct UnionFind {
  vector<int> par;

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

  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;
  }

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

int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);

  int n, m;
  cin >> n >> m;

  UnionFind uf(n + 1);
  for (int i = 0; i < m; ++i) {
    int a, b;
    cin >> a >> b;
    uf.unite(a, b);
  }
  std::set<int> roots;
  for (int i = 1; i < n + 1; ++i) {
    roots.insert(uf.root(i));
  }

  cout << roots.size() - 1 << endl;

  return 0;
}


#include <bits/stdc++.h>

#ifdef DEBUG
#define PRINT(x)                                                               \\
  cout << "func " << __func__ << ": line " << __LINE__ << ": " << #x << " = "  \\
       << (x) << endl;
#define PRINTA(a, first, last)                                                 \\
  cout << "func " << __func__ << ": line " << __LINE__ << ": " << #a << "["    \\
       << (first) << ", " << (last) << ")" << endl;                            \\
  for (int _i = (first); _i < (last); ++_i) {                                  \\
    cout << #a << "[" << _i << "] = " << (a)[_i] << endl;                      \\
  }
#define PRINTI(a, i)                                                           \\
  cout << "func " << __func__ << ": line " << __LINE__ << ": " << #a << "["    \\
       << #i << "] = " << #a << "[" << (i) << "] = " << (a)[i] << endl;
#define dprintf(...) printf(__VA_ARGS__)
#else
#define PRINT(x)
#define PRINTA(a, first, last)
#define PRINTI(a, i)
#define dprintf(...)
#endif

#define FOR(i, a, b) for (int i = (a); i < (b); ++i)
#define RFOR(i, a, b) for (int i = (b)-1; i >= (a); --i)
#define REP(i, n) for (int i = 0; i < (n); ++i)
#define RREP(i, n) for (int i = (n)-1; i >= 0; --i)
#define pb push_back
#define mp make_pair
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define MOD 1000000007
// #define MOD 998244353

using namespace std;

using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;

template <class T, class U> void amax(T &x, U y) {
  if (x < y)
    x = y;
}
template <class T, class U> void amin(T &x, U y) {
  if (x > y)
    x = y;
}

template <class T, class U>
std::ostream &operator<<(std::ostream &os, const std::pair<T, U> &p) {
  os << "(" << p.first << ", " << p.second << ")";
  return os;
}

template <class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
  os << "[";
  for (int i = 0; i < v.size(); ++i) {
    if (i) {
      if (i % 5 == 0) {
        os << ",\\n ";
      } else {
        os << ", ";
      }
    }
    os << v[i];
  }
  os << "]";
  return os;
}

template <class T> using vec2 = vector<vector<T>>;

template <class T> using vec3 = vector<vector<vector<T>>>;

template <class T> vec2<T> vec2_init(int n0, int n1, T x = 0) {
  return vec2<T>(n0, vector<T>(n1, x));
}

template <class T> vec3<T> vec3_init(int n0, int n1, int n2, T x = 0) {
  return vec3<T>(n0, vec2_init(n1, n2, x));
}

/*
 *           U P
 *           - x
 *            ^
 *            |
 * LEFT -y <--+--> +y RIGHT
 *            |
 *            v
 *           + x
 *         D O W N
 */

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

enum {
  DOWN,
  RIGHT,
  UP,
  LEFT,
};

constexpr double inf = numeric_limits<double>::infinity();

class disjoint_set {
private:
  // if vertex i is a root, r[i] = -(size of the component which has i),
  // eles r[i] = the root of i.
  std::vector<int> r;

public:
  const size_t n_vertices;
  size_t n_components;

  disjoint_set(size_t n_vertices)
      : n_vertices(n_vertices), n_components(n_vertices), r(n_vertices, -1) {}

  size_t root(size_t x) {
    if (r[x] < 0) {
      return x;
    } else {
      return r[x] = root(r[x]);
    }
  }

  void merge(size_t x, size_t y) {
    x = root(x);
    y = root(y);
    if (x == y) {
      return;
    }
    if (r[x] < r[y]) {
      r[x] += r[y];
      r[y] = x;
    } else {
      r[y] += r[x];
      r[x] = y;
    }
    --n_components;
  }

  bool is_same(size_t x, size_t y) { return root(x) == root(y); }

  size_t component_size(size_t x) { return -r[root(x)]; }
};

int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);

  int N, M;
  cin >> N >> M;
  disjoint_set ds(N);
  REP(_, M) {
    int A, B;
    cin >> A >> B;
    ds.merge(A, B);
  }
  int result = ds.n_components - 1;
  cout << result << endl;
  return 0;
}

#include <bits/stdc++.h>

#ifdef DEBUG
#define PRINT(x)                                                               \\
  cout << "func " << __func__ << ": line " << __LINE__ << ": " << #x << " = "  \\
       << (x) << endl;
#define PRINTA(a, first, last)                                                 \\
  cout << "func " << __func__ << ": line " << __LINE__ << ": " << #a << "["    \\
       << (first) << ", " << (last) << ")" << endl;                            \\
  for (int _i = (first); _i < (last); ++_i) {                                  \\
    cout << #a << "[" << _i << "] = " << (a)[_i] << endl;                      \\
  }
#define PRINTI(a, i)                                                           \\
  cout << "func " << __func__ << ": line " << __LINE__ << ": " << #a << "["    \\
       << #i << "] = " << #a << "[" << (i) << "] = " << (a)[i] << endl;
#define dprintf(...) printf(__VA_ARGS__)
#else
#define PRINT(x)
#define PRINTA(a, first, last)
#define PRINTI(a, i)
#define dprintf(...)
#endif

#define FOR(i, a, b) for (int i = (a); i < (b); ++i)
#define RFOR(i, a, b) for (int i = (b)-1; i >= (a); --i)
#define REP(i, n) for (int i = 0; i < (n); ++i)
#define RREP(i, n) for (int i = (n)-1; i >= 0; --i)
#define pb push_back
#define mp make_pair
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define MOD 1000000007
// #define MOD 998244353

using namespace std;

using ll = long long;
using ull = unsigned long long;
using pii = pair<int, int>;

template <class T, class U> void amax(T &x, U y) {
  if (x < y)
    x = y;
}
template <class T, class U> void amin(T &x, U y) {
  if (x > y)
    x = y;
}

template <class T, class U>
std::ostream &operator<<(std::ostream &os, const std::pair<T, U> &p) {
  os << "(" << p.first << ", " << p.second << ")";
  return os;
}

template <class T>
std::ostream &operator<<(std::ostream &os, const std::vector<T> &v) {
  os << "[";
  for (int i = 0; i < v.size(); ++i) {
    if (i) {
      if (i % 5 == 0) {
        os << ",\\n ";
      } else {
        os << ", ";
      }
    }
    os << v[i];
  }
  os << "]";
  return os;
}

template <class T> using vec2 = vector<vector<T>>;

template <class T> using vec3 = vector<vector<vector<T>>>;

template <class T> vec2<T> vec2_init(int n0, int n1, T x = 0) {
  return vec2<T>(n0, vector<T>(n1, x));
}

template <class T> vec3<T> vec3_init(int n0, int n1, int n2, T x = 0) {
  return vec3<T>(n0, vec2_init(n1, n2, x));
}

/*
 *           U P
 *           - x
 *            ^
 *            |
 * LEFT -y <--+--> +y RIGHT
 *            |
 *            v
 *           + x
 *         D O W N
 */

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

enum {
  DOWN,
  RIGHT,
  UP,
  LEFT,
};

constexpr double inf = numeric_limits<double>::infinity();

class disjoint_set {
private:
  // if vertex i is a root, r[i] = -(size of the component which has i),
  // eles r[i] = the root of i.
  std::vector<int> r;

public:
  const size_t n_vertices;
  size_t n_components;

  disjoint_set(size_t n_vertices)
      : n_vertices(n_vertices), n_components(n_vertices), r(n_vertices, -1) {}

  size_t root(size_t x) {
    if (r[x] < 0) {
      return x;
    } else {
      return r[x] = root(r[x]);
    }
  }

  void merge(size_t x, size_t y) {
    x = root(x);
    y = root(y);
    if (x == y) {
      return;
    }
    if (r[x] < r[y]) {
      r[x] += r[y];
      r[y] = x;
    } else {
      r[y] += r[x];
      r[x] = y;
    }
    --n_components;
  }

  bool is_same(size_t x, size_t y) { return root(x) == root(y); }

  size_t component_size(size_t x) { return -r[root(x)]; }
};

int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);

  int N, M;
  cin >> N >> M;
  disjoint_set ds(N);
  REP(_, M) {
    int A, B;
    cin >> A >> B;
    --A;
    --B;
    ds.merge(A, B);
  }
  int result = ds.n_components - 1;
  cout << result << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, n) for (ll i = 0; i < (n); i++)
const ll INF = 1LL << 60;

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;
  ll A[M], B[M];
  UnionFind tree(N);
  rep(i, M) {
    cin >> A[i] >> B[i];
    tree.unite(A[i], B[i]);
  }
  vector<ll> dist(N, 0);
  rep(i, N) { dist[tree.par[i]] = 1; }

  ll ans = 0;
  rep(i, N) {
    if (dist[i] != 0) {
      ans++;
    }
    // cout<<dist[i]<<endl;
  }
  ans = ans - 1;
  cout << ans << endl;
}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, n) for (ll i = 0; i < (n); i++)
const ll INF = 1LL << 60;

int main() {
  ll N, M;
  cin >> N >> M;
  vector<vector<ll>> A(N + 1, vector<ll>(0, 0));
  ll a, b;
  rep(i, M) {
    cin >> a >> b;
    A[a].push_back(b);
    A[b].push_back(a);
  }

  vector<ll> dist(N + 1, -1);
  queue<ll> que;
  ll ans = 0;
  for (ll i = 1; i < N + 1; i++) {
    if (dist[i] != -1) {
      continue;
    }
    if (dist[i] == -1) {
      ans++;
    }
    que.push(i);
    dist[i] = ans;
    while (!que.empty()) {
      ll v = que.front();
      que.pop();

      for (ll j = 0; j < (ll)A[v].size(); j++) {
        if (dist[A[v][j]] != -1) {
          continue;
        }
        que.push(A[v][j]);
        dist[A[v][j]] = ans;
      }
    }
  }

  cout << ans - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
#define REP(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define FOR(i, a, b) for (int i = (a), i##_len = (b); i < i##_len; i++)
#define ALL(x) (x).begin(), (x).end() // sortなどの引数を省略したい
#ifdef _DEBUG
#define PRE_COMMAND                                                            \\
  std::cin.rdbuf(in.rdbuf());                                                  \\
  std::cout << fixed << setprecision(15);
#else
#define PRE_COMMAND cout << fixed << setprecision(15);
#endif
const double EPS = 1e-10, PI = acos(-1.0);
template <class T> auto MAX(T &seq) {
  return *max_element(seq.begin(), seq.end());
}
template <class T> auto MIN(T &seq) {
  return *min_element(seq.begin(), seq.end());
}
template <class T> auto SUM(T &seq) {
  T temp{0};
  auto &temp2 = temp[0];
  return accumulate(seq.begin(), seq.end(), temp2);
}
template <class T> void SORT(T &seq) { sort(seq.begin(), seq.end()); }
template <class T, class S> void SORT(T &seq, S &sort_order) {
  sort(seq.begin(), seq.end(), sort_order);
}
template <class T> void SORTR(vector<T> &seq) {
  sort(seq.begin(), seq.end(), greater<T>());
}
template <class T, class S, class R> long long pow(T n_0, S k_0, R mod_0) {
  long long n = n_0 % mod_0, k = k_0, mod = mod_0, now = 1;
  while (k) {
    if (k & 1)
      now = (now * n) % mod;
    n = (n * n) % mod;
    k >>= 1;
  }
  return now;
}
template <typename T> istream &operator>>(istream &is, vector<T> &vec) {
  for (T &x : vec)
    is >> x;
  return is;
}

int main() {
  PRE_COMMAND
  int n, m, a, b;
  cin >> n >> m;
  struct unionfind { // size:要素数,tree：unionfind木
    int n;
    vector<int> tree_leader, tree_size;
    unionfind(int temp) : n(temp) {
      tree_leader.assign(n, 0);
      tree_size.assign(n, 1);
      for (int i = 0; i < n; i++) {
        tree_leader[i] = i;
      }
    }
    // 結合
    void merge(int index1, int index2) {
      int l1 = leader(index1), l2 = leader(index2);
      if (l1 != l2) {
        if (tree_size[l1] <= tree_size[l2]) {
          tree_leader[l1] = l2;
          tree_size[l2] = tree_size[l1] + tree_size[l2];
        } else {
          tree_leader[l2] = l1;
          tree_size[l1] = tree_size[l1] + tree_size[l2];
        }
      }
    }
    // 同じか判定
    bool same(int index1, int index2) {
      return leader(index1) == leader(index2);
    }
    // leaderを探す
    int leader(int index) {
      vector<int> temp_list(0);
      int temp = tree_leader[index];
      while (index != temp) {
        temp_list.push_back(index);
        index = temp;
        temp = tree_leader[index];
      }
      for (int i : temp_list) {
        tree_leader[i] = index;
      }
      return index;
    }
    // 連結成分のサイズ
    int size(int index) { return tree_size[leader(index)]; }
    // 「一つの連結成分の頂点番号のリスト」のリスト
    vector<vector<int>> groups() {
      int cnt = -1;
      vector<int> id_list(n, -1);
      vector<vector<int>> ret;
      for (int i = 0; i < n; i++) {
        int l = leader(i);
        if (id_list[l] == -1) {
          id_list[l] = ++cnt;
          ret.push_back({i});
        } else {
          ret[id_list[l]].push_back(i);
        }
      }
      return ret;
    }
    // 連結成分の個数
    int component() { return (int)groups().size(); }
  };
  unionfind uf(n);
  REP(i, m) {
    cin >> a >> b;
    uf.merge(a, b);
  }
  cout << uf.component() - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
#define REP(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define FOR(i, a, b) for (int i = (a), i##_len = (b); i < i##_len; i++)
#define ALL(x) (x).begin(), (x).end() // sortなどの引数を省略したい
#ifdef _DEBUG
#define PRE_COMMAND                                                            \\
  std::cin.rdbuf(in.rdbuf());                                                  \\
  std::cout << fixed << setprecision(15);
#else
#define PRE_COMMAND cout << fixed << setprecision(15);
#endif
const double EPS = 1e-10, PI = acos(-1.0);
template <class T> auto MAX(T &seq) {
  return *max_element(seq.begin(), seq.end());
}
template <class T> auto MIN(T &seq) {
  return *min_element(seq.begin(), seq.end());
}
template <class T> auto SUM(T &seq) {
  T temp{0};
  auto &temp2 = temp[0];
  return accumulate(seq.begin(), seq.end(), temp2);
}
template <class T> void SORT(T &seq) { sort(seq.begin(), seq.end()); }
template <class T, class S> void SORT(T &seq, S &sort_order) {
  sort(seq.begin(), seq.end(), sort_order);
}
template <class T> void SORTR(vector<T> &seq) {
  sort(seq.begin(), seq.end(), greater<T>());
}
template <class T, class S, class R> long long pow(T n_0, S k_0, R mod_0) {
  long long n = n_0 % mod_0, k = k_0, mod = mod_0, now = 1;
  while (k) {
    if (k & 1)
      now = (now * n) % mod;
    n = (n * n) % mod;
    k >>= 1;
  }
  return now;
}
template <typename T> istream &operator>>(istream &is, vector<T> &vec) {
  for (T &x : vec)
    is >> x;
  return is;
}

int main() {
  PRE_COMMAND
  int n, m, a, b;
  cin >> n >> m;
  struct unionfind { // size:要素数,tree：unionfind木
    int n;
    vector<int> tree_leader, tree_size;
    unionfind(int temp) : n(temp) {
      tree_leader.assign(n, 0);
      tree_size.assign(n, 1);
      for (int i = 0; i < n; i++) {
        tree_leader[i] = i;
      }
    }
    // 結合
    void merge(int index1, int index2) {
      int l1 = leader(index1), l2 = leader(index2);
      if (l1 != l2) {
        if (tree_size[l1] <= tree_size[l2]) {
          tree_leader[l1] = l2;
          tree_size[l2] = tree_size[l1] + tree_size[l2];
        } else {
          tree_leader[l2] = l1;
          tree_size[l1] = tree_size[l1] + tree_size[l2];
        }
      }
    }
    // 同じか判定
    bool same(int index1, int index2) {
      return leader(index1) == leader(index2);
    }
    // leaderを探す
    int leader(int index) {
      vector<int> temp_list(0);
      int temp = tree_leader[index];
      while (index != temp) {
        temp_list.push_back(index);
        index = temp;
        temp = tree_leader[index];
      }
      for (int i : temp_list) {
        tree_leader[i] = index;
      }
      return index;
    }
    // 連結成分のサイズ
    int size(int index) { return tree_size[leader(index)]; }
    // 「一つの連結成分の頂点番号のリスト」のリスト
    vector<vector<int>> groups() {
      int cnt = -1;
      vector<int> id_list(n, -1);
      vector<vector<int>> ret;
      for (int i = 0; i < n; i++) {
        int l = leader(i);
        if (id_list[l] == -1) {
          id_list[l] = ++cnt;
          ret.push_back({i});
        } else {
          ret[id_list[l]].push_back(i);
        }
      }
      return ret;
    }
    // 連結成分の個数
    int component() { return (int)groups().size(); }
  };
  unionfind uf(n);
  REP(i, m) {
    cin >> a >> b;
    uf.merge(a - 1, b - 1);
  }
  cout << uf.component() - 1 << endl;
}


#include <iostream>
#include <queue>
#include <vector>
using namespace std;

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> adj[n];
  int u, v;
  for (int i = 0; i < m; i++) {
    cin >> u >> v;
    adj[u].push_back(v);
    adj[v].push_back(u);
  }

  int visited[n], cmp[n], freq[n];
  for (int i = 0; i < n; i++) {
    visited[i] = 0;
    cmp[i] = 0;
    freq[i] = 0;
  }

  for (int i = 0; i < n; i++) {
    if (visited[i])
      continue;
    queue<int> q;
    q.push(i);
    visited[i] = 1;
    cmp[i] = i;
    while (!q.empty()) {
      u = q.front();
      q.pop();
      for (auto v : adj[u]) {
        if (visited[i] == 0) {
          q.push(v);
          visited[v] = 1;
          cmp[v] = i;
        }
      }
    }
  }

  int count = 0;
  for (int i = 0; i < n; i++) {
    if (cmp[i] != -1)
      freq[cmp[i]]++;
  }

  for (int i = 0; i < n; i++) {
    if (freq[i] > 0)
      count++;
  }

  cout << count - 1 << endl;
}

#include <iostream>
#include <queue>
#include <vector>
using namespace std;

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> adj[n];
  int u, v;
  for (int i = 0; i < m; i++) {
    cin >> u >> v;
    adj[u - 1].push_back(v - 1);
    adj[v - 1].push_back(u - 1);
  }

  int visited[n], cmp[n], freq[n];
  for (int i = 0; i < n; i++) {
    visited[i] = 0;
    cmp[i] = -1;
    freq[i] = 0;
  }

  for (int i = 0; i < n; i++) {
    if (visited[i])
      continue;
    queue<int> q;
    q.push(i);
    visited[i] = 1;
    cmp[i] = i;
    while (!q.empty()) {
      u = q.front();
      q.pop();
      for (auto v : adj[u]) {
        if (visited[v] == 0) {
          q.push(v);
          visited[v] = 1;
          cmp[v] = i;
        }
      }
    }
  }

  int count = 0;
  for (int i = 0; i < n; i++) {
    if (cmp[i] != -1)
      freq[cmp[i]]++;
  }

  for (int i = 0; i < n; i++) {
    // cout << "cmp and freq " << cmp[i] << " " << freq[i] << endl;
    if (freq[i] > 0)
      count++;
  }

  cout << count - 1 << endl;
}


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

#define ll long long
#define ar array
const int mxn = 1e5;
ll n, m;
vector<ll> g[mxn];
vector<bool> used(mxn, false);
int ans = 0;
void dfs(int u) {
  used[u] = true;
  for (ll i : g[u])
    if (!used[i])
      dfs(i);
}
void solve() {
  cin >> n >> m;
  while (m--) {
    ll a, b;
    cin >> a >> b;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  for (ll i = 0; i < n; i++)
    if (!used[i])
      dfs(i), ++ans;
  for (int i = 0; i < n; i++)
    if (!used[i])
      ++ans;
  cout << ans;
}
int main() { solve(); }

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

#define ll long long
#define ar array
const int mxn = 1e5 + 1;
ll n, m;
vector<ll> g[mxn];
vector<bool> used(mxn, false);
int ans = 0;
void dfs(int u) {
  used[u] = true;
  for (ll i : g[u])
    if (!used[i])
      dfs(i);
}
void solve() {
  cin >> n >> m;
  while (m--) {
    ll a, b;
    cin >> a >> b;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  for (ll i = 1; i <= n; i++)
    if (!used[i])
      dfs(i), ++ans;
  cout << ans - 1;
}
int main() { solve(); }


#include <bits/stdc++.h>
using namespace std;
#define REP(i, x, n) for (int i = x; i < (int)n; i++)
#define rep(i, n) REP(i, 0, n)
#define sp(p) cout << setprecision(16) << fixed << p << endl;
#define all(a) a.begin(), a.end()
#define rall(a) a.rbegin(), a.rend()
#define SORT(a) sort(all(a))
#define RSORT(a) sort(rall(a))
#define UNIQ(a) unique(all(a))
#define UNIQNUM(a) UNIQ(a) - a.begin()
#define UNIQIT(a) a.erase(UNIQ(a), a.end());
#define VOUT(v, i)                                                             \\
  rep(i, v.size()) cout << v[i] << (i == v.size() - 1 ? "\\n" : " ");
#define vout(v) VOUT(v, z);
#define vdbg(v, i)                                                             \\
  cout << #v << ": ";                                                          \\
  for (int i = 0; i < (int)v.size(); i++) {                                    \\
    cout << v[i] << " ";                                                       \\
  }                                                                            \\
  cout << "\\n";
#define vmin(v) *min_element(all(v))
#define vmax(v) *max_element(all(v))
#define vsum(v) accumulate(all(v), 0LL)
#define MOUT(m, r, c)                                                          \\
  rep(i, r) {                                                                  \\
    rep(j, c) { cout << m[i][j] << " "; }                                      \\
    cout << endl;                                                              \\
  }
#define mout(m) MOUT(m, m.size(), m[0].size())
#define debg(a) cout << #a << ": " << a << endl;
#define out(a) debg(a);
#define aout(a)                                                                \\
  for (auto &e : a)                                                            \\
    cout << e << " ";
#define show(a)                                                                \\
  for (cont & y : a) {                                                         \\
    for (cont & x : y) {                                                       \\
      cout << x << " ";                                                        \\
    }                                                                          \\
    cout << endl;                                                              \\
  }
#define digit(a) to_string(a).length();
// template<class T>inline int out(const T &t){ print(t); putchar('\\n'); return
// 0; } template<class T>inline T gcd(T a,T b){if(b==0)return a;
// return(gcd(b,a%b));} template<class T>inline T lcm(T a,T b){return
// a/gcd(a,b)*b;}
bool is_palindrome(string s) { return s == string(s.rbegin(), s.rend()); }
#define popcount __builtin_popcount
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> P;
typedef vector<ll> V;
typedef vector<vector<ll>> VV;
// const long long MOD=1000000007;
const long long INF = 1e18;
#define EPS (1e-7)
#define PI (acos(-1))
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}

// Implement (union by size) + (path compression)
// Reference:
// Zvi Galil and Giuseppe F. Italiano,
// Data structures and algorithms for disjoint set union problems
struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

int main() {
  long long N;
  scanf("%lld", &N);
  long long M;
  scanf("%lld", &M);
  std::vector<long long> A(M);
  std::vector<long long> B(M);
  for (int i = 0; i < M; i++) {
    scanf("%lld", &A[i]);
    scanf("%lld", &B[i]);
  }
  dsu d(N);
  for (long long i = 0; i < M; i++) {
    d.merge(A[i], B[i]);
  }
  vector<vector<int>> v = d.groups();
  // out(v.size());
  cout << v.size() - 1 << endl;

  return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define REP(i, x, n) for (int i = x; i < (int)n; i++)
#define rep(i, n) REP(i, 0, n)
#define sp(p) cout << setprecision(16) << fixed << p << endl;
#define all(a) a.begin(), a.end()
#define rall(a) a.rbegin(), a.rend()
#define SORT(a) sort(all(a))
#define RSORT(a) sort(rall(a))
#define UNIQ(a) unique(all(a))
#define UNIQNUM(a) UNIQ(a) - a.begin()
#define UNIQIT(a) a.erase(UNIQ(a), a.end());
#define VOUT(v, i)                                                             \\
  rep(i, v.size()) cout << v[i] << (i == v.size() - 1 ? "\\n" : " ");
#define vout(v) VOUT(v, z);
#define vdbg(v, i)                                                             \\
  cout << #v << ": ";                                                          \\
  for (int i = 0; i < (int)v.size(); i++) {                                    \\
    cout << v[i] << " ";                                                       \\
  }                                                                            \\
  cout << "\\n";
#define vmin(v) *min_element(all(v))
#define vmax(v) *max_element(all(v))
#define vsum(v) accumulate(all(v), 0LL)
#define MOUT(m, r, c)                                                          \\
  rep(i, r) {                                                                  \\
    rep(j, c) { cout << m[i][j] << " "; }                                      \\
    cout << endl;                                                              \\
  }
#define mout(m) MOUT(m, m.size(), m[0].size())
#define debg(a) cout << #a << ": " << a << endl;
#define out(a) debg(a);
#define aout(a)                                                                \\
  for (auto &e : a)                                                            \\
    cout << e << " ";
#define show(a)                                                                \\
  for (cont & y : a) {                                                         \\
    for (cont & x : y) {                                                       \\
      cout << x << " ";                                                        \\
    }                                                                          \\
    cout << endl;                                                              \\
  }
#define digit(a) to_string(a).length();
// template<class T>inline int out(const T &t){ print(t); putchar('\\n'); return
// 0; } template<class T>inline T gcd(T a,T b){if(b==0)return a;
// return(gcd(b,a%b));} template<class T>inline T lcm(T a,T b){return
// a/gcd(a,b)*b;}
bool is_palindrome(string s) { return s == string(s.rbegin(), s.rend()); }
#define popcount __builtin_popcount
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll> P;
typedef vector<ll> V;
typedef vector<vector<ll>> VV;
// const long long MOD=1000000007;
const long long INF = 1e18;
#define EPS (1e-7)
#define PI (acos(-1))
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}

// Implement (union by size) + (path compression)
// Reference:
// Zvi Galil and Giuseppe F. Italiano,
// Data structures and algorithms for disjoint set union problems
struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

int main() {
  long long N;
  scanf("%lld", &N);
  long long M;
  scanf("%lld", &M);
  std::vector<long long> A(M);
  std::vector<long long> B(M);
  for (int i = 0; i < M; i++) {
    scanf("%lld", &A[i]);
    scanf("%lld", &B[i]);
  }
  dsu d(N);
  for (long long i = 0; i < M; i++) {
    d.merge(A[i] - 1, B[i] - 1);
  }
  vector<vector<int>> v = d.groups();
  // out(v.size());
  cout << v.size() - 1 << endl;

  return 0;
}


#include <iostream>
#include <set>
using namespace std;
#define ll long long
#define vvll vector<vector<long long>>
#define vll vector<long long>
#define fri(i, n) for (int i = 0; i < n; i++)
#define fri1(i, n) for (int i = n - 1; i >= 0; i--)
#define map map<ll, ll>
#define pb push_back
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);
#define ump unordered_map<long long, long long>
#define ub upper_bound
#define bsearch binary_search

ll parent[100001] = {0};
int find(int x) {
  while (parent[x] != x) {
    parent[x] = find(parent[x]);
  }
  return parent[x];
}
int main() {
  fast ll n, m, x, y;
  cin >> n >> m;
  fri(i, n + 1) { parent[i] = i; }
  fri(i, m) {
    cin >> x >> y;
    if (find(y) != find(x)) {
      parent[y] = find(x);
    }
  }
  set<int> st;
  for (int i = 1; i <= n; i++) {
    st.insert(parent[i]);
  }
  cout << st.size() - 1 << "\\n";
  return 0;
}

#include <iostream>
#include <set>
using namespace std;
#define ll long long
#define vvll vector<vector<long long>>
#define vll vector<long long>
#define fri(i, n) for (int i = 0; i < n; i++)
#define fri1(i, n) for (int i = n - 1; i >= 0; i--)
#define map map<ll, ll>
#define pb push_back
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);
#define ump unordered_map<long long, long long>
#define ub upper_bound
#define bsearch binary_search

ll parent[100001] = {0};

int find(int x) {
  if (parent[x] != x) {
    parent[x] = find(parent[x]);
  }
  return parent[x];
}

int main() {
  fast ll n, m, x, y;
  cin >> n >> m;
  fri(i, n + 1) { parent[i] = i; }
  fri(i, m) {
    cin >> x >> y;
    if (find(y) != find(x)) {
      parent[find(y)] = find(x);
    }
  }
  set<int> st, st1;
  for (int i = 1; i <= n; i++) {
    st.insert(parent[i]);
  }
  for (auto it : st) {
    st1.insert(find(it));
  }
  cout << st1.size() - 1 << "\\n";
  return 0;
}


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

using ll = long long;
using vi = vector<int>;
using vvi = vector<vi>;
using vl = vector<ll>;
using vvl = vector<vl>;
using vb = vector<bool>;
using vvb = vector<vb>;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
#define rep(i, s, n) for (int i = (int)(s); i < (int)(n); i++)

ll INF = 1ll << 60;

// UnionFind木
const int MAX = 100010;
int par[MAX];

void init() {
  rep(i, 0, MAX) { par[i] = -1; }
}

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

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

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

bool same(int x, int y) {
  int rx = root(x);
  int ry = root(y);
  if (rx < 0 || ry < 0) {
    if (rx >= 0) {
      return rx == y;
    }
    if (ry >= 0) {
      return ry == x;
    }
    return false;
  }
  return rx == ry;
}

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

  init();

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

  vb ck(n, false);

  rep(i, 0, n) ck[root(i)] = true;

  int cnt = 0;

  rep(i, 0, n) {
    if (ck[i])
      cnt++;
  }

  cout << cnt - 1 << endl;

  return 0;
}

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

using ll = long long;
using vi = vector<int>;
using vvi = vector<vi>;
using vl = vector<ll>;
using vvl = vector<vl>;
using vb = vector<bool>;
using vvb = vector<vb>;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
#define rep(i, s, n) for (int i = (int)(s); i < (int)(n); i++)

ll INF = 1ll << 60;

// UnionFind木
const int MAX = 100010;
int par[MAX];

void init() {
  rep(i, 0, MAX) { par[i] = -1; }
}

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

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

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

bool same(int x, int y) {
  int rx = root(x);
  int ry = root(y);
  if (rx < 0 || ry < 0) {
    if (rx >= 0) {
      return rx == y;
    }
    if (ry >= 0) {
      return ry == x;
    }
    return false;
  }
  return rx == ry;
}

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

  init();

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

  vb ck(n, false);

  rep(i, 0, n) ck[root(i)] = true;

  int cnt = 0;

  rep(i, 0, n) {
    if (ck[i])
      cnt++;
  }

  cout << cnt - 1 << endl;

  return 0;
}


#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define int long long int
#define double long double
#define rep(i, begin, end)                                                     \\
  for (__typeof(end) i = (begin) - ((begin) > (end));                          \\
       i != (end) - ((begin) > (end)); i += 1 - 2 * ((begin) > (end)))
#define endl '\\n'
#define ff first
#define ss second
#define rev(a) a.rbegin(), a.rend()
#define all(a) a.begin(), a.end()
#define pi pair<int, int>
#define vii vector<pi>
#define pb push_back
// #define size(a)  (int)(a.size())
#define fill(s, x, n)                                                          \\
  vector<s> x(n);                                                              \\
  rep(i, 0, n) cin >> x[i];
#define debug(...)                                                             \\
  cerr << " [" << #__VA_ARGS__ << ": " << (__VA_ARGS__) << "] ";
#define debugv(...)                                                            \\
  for (int i = 0; i < size(__VA_ARGS__); i++)                                  \\
    cerr << " [" << #__VA_ARGS__ << i << ": " << (__VA_ARGS__)[i] << "] ";
const int mod = 1e8;
const int inf = 1e9 + 5;
const int inf64 = 1e18 + 5;
//           -------"Where is your Pen and Paper?"-------
void solve() {
  int n, m;
  cin >> n >> m;
  vector<vector<int>> adj(n + 1, vector<int>(n + 1, 1));
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    adj[a][b] = 0;
    adj[b][a] = 0;
  }
  int total_weight = 0;
  vector<int> min_e(n + 1, inf);
  min_e[1] = 0;
  set<pi> q;
  q.insert({0, 1});
  vector<bool> selected(n + 1, false);
  for (int i = 1; i <= n; ++i) {
    int v = q.begin()->second;
    selected[v] = true;
    total_weight += q.begin()->first;
    q.erase(q.begin());

    // if (min_e[v].to != -1)
    //     cout << v << " " << min_e[v].to << endl;

    for (int i = 1; i <= n; i++) {
      int w = adj[v][i];
      if (!selected[i] && w < min_e[i]) {
        q.erase({min_e[i], i});
        min_e[i] = w;
        q.insert({w, i});
      }
    }
  }

  cout << total_weight << endl;
}

int32_t main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  /*
  #ifndef ONLINE_JUDGE
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
  #endif
  */
  int test;
  // cin>>test;
  test = 1;
  // int cse=1;
  while (test--) {
    // cout<<"Case #"<<cse<<": ";
    solve();
    cout << endl;
    // cse++;
  }
  cerr << "Time : " << 1000 * ((double)clock()) / (double)CLOCKS_PER_SEC
       << "ms\\n";
  return 0;
}

#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define int long long int
#define double long double
#define rep(i, begin, end)                                                     \\
  for (__typeof(end) i = (begin) - ((begin) > (end));                          \\
       i != (end) - ((begin) > (end)); i += 1 - 2 * ((begin) > (end)))
#define endl '\\n'
#define ff first
#define ss second
#define rev(a) a.rbegin(), a.rend()
#define all(a) a.begin(), a.end()
#define pi pair<int, int>
#define vii vector<pi>
#define pb push_back
// #define size(a)  (int)(a.size())
#define fill(s, x, n)                                                          \\
  vector<s> x(n);                                                              \\
  rep(i, 0, n) cin >> x[i];
#define debug(...)                                                             \\
  cerr << " [" << #__VA_ARGS__ << ": " << (__VA_ARGS__) << "] ";
#define debugv(...)                                                            \\
  for (int i = 0; i < size(__VA_ARGS__); i++)                                  \\
    cerr << " [" << #__VA_ARGS__ << i << ": " << (__VA_ARGS__)[i] << "] ";
const int mod = 1e8;
const int inf = 1e9 + 5;
const int inf64 = 1e18 + 5;
//           -------"Where is your Pen and Paper?"-------
vector<int> parent(100005);
int find_set(int v) {
  if (v == parent[v])
    return v;
  return parent[v] = find_set(parent[v]);
}
void union_sets(int a, int b) {
  a = find_set(a);
  b = find_set(b);
  if (a != b)
    parent[b] = a;
}
void solve() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    parent[i] = i;
  }
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    union_sets(a, b);
  }
  unordered_set<int> s;
  for (int i = 1; i <= n; i++) {
    find_set(i);
    s.insert(parent[i]);
  }
  cout << s.size() - 1;
}

int32_t main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  /*
  #ifndef ONLINE_JUDGE
  freopen("input.txt", "r", stdin);
  freopen("output.txt", "w", stdout);
  #endif
  */
  int test;
  // cin>>test;
  test = 1;
  // int cse=1;
  while (test--) {
    // cout<<"Case #"<<cse<<": ";
    solve();
    cout << endl;
    // cse++;
  }
  cerr << "Time : " << 1000 * ((double)clock()) / (double)CLOCKS_PER_SEC
       << "ms\\n";
  return 0;
}


#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp> // Common file
#include <ext/pb_ds/detail/standard_policies.hpp>
#include <ext/pb_ds/tree_policy.hpp> // Including tree_order_statistics_node_update

/*
 *    coder :: ATUL_PANDEY_2608
 *        >>> INDIA <<<
 */
using namespace std;
using namespace __gnu_pbds;

// #define  part ..
#define pb(a) push_back(a)
#define all(a) a.begin(), a.end()
#define mod 1000000007
// #define maxx 200006
#define ll long long
#define quick ios_base::sync_with_stdio(NULL), cin.tie(0);
#define listll vector<long long>
#define listi vector<int>
#define pii pair<int, int>
#define pll pair<long long, long long>
#define minheap priority_queue<long long, vector<long long>, greater<long long>>
#define rep(i, a, b) for (int i = a; i < b; i++)

ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

ll power(ll a, ll n, ll m = 1000000007) {
  ll ans = 1;
  while (n) {
    if (n & 1) {
      ans *= a;
      ans %= m;
    }
    a *= a;
    a %= m;
    n /= 2;
  }
  return ans;
}

vector<int> prm;
vector<bool> vsp;

void prime(int nn = 100000) {
  vsp = vector<bool>(nn + 1, 0);
  int size_prime = nn;
  for (ll i = 2; i * i < size_prime; i++) {

    if (vsp[i] == 1)
      continue;
    for (ll j = i * i; j < size_prime; j += i) {

      vsp[j] = 1;
    }
  }

  for (int i = 2; i < size_prime; i++)
    if (vsp[i] == 0)
      prm.pb(i);
}

template <class T>
using oset =
    tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

// end of #define

// define globals ...
const int maxx = 2e5 + 26;

// write function from here ...
//  never forget  to recheck your predefined function in template ..
//  already defined ::  prime , power , gcd : ( ll )..

vector<int> adj[maxx];

vector<int> vis;
void dfs(int u, int par) {

  vis[u] = 1;
  if (adj[u].size() == 0)
    return;
  for (auto it : adj[u]) {

    if (it != par) {
      dfs((int)it, u);
    }
  }
};

int Main() {

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

    cin >> a >> b;
    a--, b--;
    adj[a].pb(b);
    adj[b].pb(a);
  }

  vis = vector<int>(maxx, 0);

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

    if (vis[i] == 0) {

      dfs(i, -1);
      cnt++;
    }
  }

  cout << cnt - 1 << endl;

  return 0;
}

int main() {
  quick;
  int t = 1;
  // cin>>t;
  while (t--)
    Main();

  return 0;
}

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp> // Common file
#include <ext/pb_ds/detail/standard_policies.hpp>
#include <ext/pb_ds/tree_policy.hpp> // Including tree_order_statistics_node_update

/*
 *    coder :: ATUL_PANDEY_2608
 *        >>> INDIA <<<
 */
using namespace std;
using namespace __gnu_pbds;

// #define  part ..
#define pb(a) push_back(a)
#define all(a) a.begin(), a.end()
#define mod 1000000007
// #define maxx 200006
#define ll long long
#define quick ios_base::sync_with_stdio(NULL), cin.tie(0);
#define listll vector<long long>
#define listi vector<int>
#define pii pair<int, int>
#define pll pair<long long, long long>
#define minheap priority_queue<long long, vector<long long>, greater<long long>>
#define rep(i, a, b) for (int i = a; i < b; i++)

ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }

ll power(ll a, ll n, ll m = 1000000007) {
  ll ans = 1;
  while (n) {
    if (n & 1) {
      ans *= a;
      ans %= m;
    }
    a *= a;
    a %= m;
    n /= 2;
  }
  return ans;
}

vector<int> prm;
vector<bool> vsp;

void prime(int nn = 100000) {
  vsp = vector<bool>(nn + 1, 0);
  int size_prime = nn;
  for (ll i = 2; i * i < size_prime; i++) {

    if (vsp[i] == 1)
      continue;
    for (ll j = i * i; j < size_prime; j += i) {

      vsp[j] = 1;
    }
  }

  for (int i = 2; i < size_prime; i++)
    if (vsp[i] == 0)
      prm.pb(i);
}

template <class T>
using oset =
    tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

// end of #define

// define globals ...
const int maxx = 2e5 + 26;

// write function from here ...
//  never forget  to recheck your predefined function in template ..
//  already defined ::  prime , power , gcd : ( ll )..

vector<int> adj[maxx];

vector<int> vis;
void dfs(int u, int par) {

  vis[u] = 1;
  if (adj[u].size() == 0)
    return;
  for (auto it : adj[u]) {

    if (it != par && vis[it] == 0) {
      dfs((int)it, u);
    }
  }
};

int Main() {

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

    cin >> a >> b;
    a--, b--;
    adj[a].pb(b);
    adj[b].pb(a);
  }

  vis = vector<int>(maxx, 0);

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

    if (vis[i] == 0) {

      dfs(i, -1);
      cnt++;
    }
  }

  cout << cnt - 1 << endl;

  return 0;
}

int main() {
  quick;
  int t = 1;
  // cin>>t;
  while (t--)
    Main();

  return 0;
}


#include <iostream>
#include <vector>

using namespace std;

struct UnionFind {
  vector<int> data;
  int ngroup;
  UnionFind(int size) : data(size, -1) {}
  bool unionSet(int x, int y) {
    x = root(x);
    y = root(y);
    if (x != y) {
      if (data[y] < data[x])
        swap(x, y);
      data[x] += data[y];
      data[y] = x;
    }
    return x != y;
  }
  bool findSet(int x, int y) { return root(x) == root(y); }
  int root(int x) { return data[x] < 0 ? x : data[x] = root(data[x]); }
  int size(int x) { return -data[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;
    uf.unionSet(a, b);
  }

  int ans = n;
  for (int i = 0; i < n; i++) {
    if (uf.root(i) == i) {
      ans--;
    }
  }
  cout << ans << endl;
  return 0;
}

#include <iostream>
#include <vector>

using namespace std;

struct UnionFind {
  vector<int> data;
  int ngroup;
  UnionFind(int size) : data(size, -1) {}
  bool unionSet(int x, int y) {
    x = root(x);
    y = root(y);
    if (x != y) {
      if (data[y] < data[x])
        swap(x, y);
      data[x] += data[y];
      data[y] = x;
    }
    return x != y;
  }
  bool findSet(int x, int y) { return root(x) == root(y); }
  int root(int x) { return data[x] < 0 ? x : data[x] = root(data[x]); }
  bool isRoot(int x) { return data[x] < 0; }
  int size(int x) { return -data[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.unionSet(a, b);
  }

  int ans = 0;
  for (int i = 0; i < n; i++) {
    if (uf.isRoot(i)) {
      ans++;
    }
  }
  ans--;
  cout << ans << endl;
  return 0;
}


#define _GLIBCXX_DEBUG
#include <algorithm> //next_permutation
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define all(v) v.begin(), v.end()
#define dec(n) cout << fixed << setprecision(n);
#define large "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define small "abcdefghijklmnopqrstuvwxyz"
using namespace std;
using ll = long long;
using P = pair<ll, ll>;
using vl = vector<ll>;
using vvl = vector<vl>;

ll gcd(ll a, ll b) {
  if (b == 0)
    return a;
  return gcd(b, a % b);
}

const ll MOD = 1000000007;
const ll MAX = 2000001;

ll mod(ll a) { return a % MOD; }

ll lcm(ll a, ll b) { return (a * b) / gcd(a, b); }

struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

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

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }

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

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

  rep(i, m) {
    ll x, y;
    cin >> x >> y;

    UF.unite(x, y);
  }

  set<ll> parent;

  rep(i, n) { parent.insert(UF.root(i)); }

  cout << parent.size() - 1 << endl;
}

#define _GLIBCXX_DEBUG
#include <algorithm> //next_permutation
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define all(v) v.begin(), v.end()
#define dec(n) cout << fixed << setprecision(n);
#define large "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#define small "abcdefghijklmnopqrstuvwxyz"
using namespace std;
using ll = long long;
using P = pair<ll, ll>;
using vl = vector<ll>;
using vvl = vector<vl>;

ll gcd(ll a, ll b) {
  if (b == 0)
    return a;
  return gcd(b, a % b);
}

const ll MOD = 1000000007;
const ll MAX = 2000001;

ll mod(ll a) { return a % MOD; }

ll lcm(ll a, ll b) { return (a * b) / gcd(a, b); }

struct UnionFind {
  // 自身が親であれば、その集合に属する頂点数に-1を掛けたもの
  // そうでなければ親のid
  vector<int> r;

  UnionFind(int N) { r = vector<int>(N, -1); }

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

  bool unite(int x, int y) {
    x = root(x);
    y = root(y);
    if (x == y)
      return false;
    if (r[x] > r[y])
      swap(x, y);
    r[x] += r[y];
    r[y] = x;
    return true;
  }

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

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

  rep(i, m) {
    ll x, y;
    cin >> x >> y;
    x--;
    y--;

    UF.unite(x, y);
  }

  set<ll> parent;

  rep(i, n) { parent.insert(UF.root(i)); }

  cout << parent.size() - 1 << endl;
}


#include <bits/stdc++.h>
typedef double D;
typedef long long int LL;
typedef long double LD;
#define OR ||
#define AND &&
#define nl '\\n'
#define S string
#define inf INT_MAX
#define SQR(a) (a) * (a)
#define pb push_back
#define GCD(a, b) __gcd(a, b)
#define PI 2.0 * acos(0.0)
#define pii pair<int, int>
#define pll pair<long long, long long>
#define LCM(a, b) (a * b) / GCD(a, b)
#define mem(a, b) memset(a, b, sizeof(a))
#define srtv(v) sort(v.begin(), v.end())
#define T                                                                      \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define Rep(i, a, b) for (int i = a; i <= b; i++)
#define rep(i, a, b) for (int i = a; i >= b; i--)
#define boost                                                                  \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)
#define inout                                                                  \\
  freopen("input.txt", "r", stdin);                                            \\
  freopen("output.txt", "w", stdout)

#define si(x) scanf("%d", &x)
#define pi(x) printf("%d", x)
#define ss(str) scanf("%s", str)
#define pl(x) printf("%lld", x)
#define sl(x) scanf("%lld", &x)
#define sii(x, y) scanf("%d %d", &x, &y)
#define sll(x, y) scanf("%lld %lld", &x, &y)
#define siii(x, y, z) scanf("%d %d %d", &x, &y, &z)
#define slll(x, y, z) scanf("%lld %lld %lld", &x, &y, &z)
using namespace std;

/*
int bs(int l,int r,int n)
{
    int mid,res=inf;

    while(l<=r)
    {
        mid=l+(r-l)/2;

        if(check(mid,n))
        {
            r=mid-1;
            res=min(res,mid);
        }
        else
            l=mid+1;

    }

    return res;
}
*/

//--------------------------code starts
//here---------------------------------------

struct Edge {
  int u, v, w;
};

bool compareByWeight(Edge a, Edge b) { return a.w < b.w; }

#define mx 30001
int parent[mx];

vector<Edge> E;

void makeSet(int u) { parent[u] = u; }

void init(int n) {
  for (int i = 0; i <= n; i++)
    makeSet(i);
}

int Find(int u) {
  if (u == parent[u])
    return u;

  return parent[u] = Find(parent[u]);
}

void Union(int u, int v) {
  int p = Find(u);
  int q = Find(v);

  if (p != q) {
    parent[q] = p;
  }
}

int main() {
  int n, m, u, v, w;
  sii(n, m);

  init(n);

  Rep(i, 1, m) {
    sii(u, v);

    Union(u, v);
  }

  set<int> st;

  Rep(i, 1, n) { st.insert(Find(i)); }

  cout << st.size() - 1;

  return 0;
}

#include <bits/stdc++.h>
typedef double D;
typedef long long int LL;
typedef long double LD;
#define OR ||
#define AND &&
#define nl '\\n'
#define S string
#define inf INT_MAX
#define SQR(a) (a) * (a)
#define pb push_back
#define GCD(a, b) __gcd(a, b)
#define PI 2.0 * acos(0.0)
#define pii pair<int, int>
#define pll pair<long long, long long>
#define LCM(a, b) (a * b) / GCD(a, b)
#define mem(a, b) memset(a, b, sizeof(a))
#define srtv(v) sort(v.begin(), v.end())
#define T                                                                      \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define Rep(i, a, b) for (int i = a; i <= b; i++)
#define rep(i, a, b) for (int i = a; i >= b; i--)
#define boost                                                                  \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)
#define inout                                                                  \\
  freopen("input.txt", "r", stdin);                                            \\
  freopen("output.txt", "w", stdout)

#define si(x) scanf("%d", &x)
#define pi(x) printf("%d", x)
#define ss(str) scanf("%s", str)
#define pl(x) printf("%lld", x)
#define sl(x) scanf("%lld", &x)
#define sii(x, y) scanf("%d %d", &x, &y)
#define sll(x, y) scanf("%lld %lld", &x, &y)
#define siii(x, y, z) scanf("%d %d %d", &x, &y, &z)
#define slll(x, y, z) scanf("%lld %lld %lld", &x, &y, &z)
using namespace std;

/*
int bs(int l,int r,int n)
{
    int mid,res=inf;

    while(l<=r)
    {
        mid=l+(r-l)/2;

        if(check(mid,n))
        {
            r=mid-1;
            res=min(res,mid);
        }
        else
            l=mid+1;

    }

    return res;
}
*/

//--------------------------code starts
//here---------------------------------------

#define mx 100005
int parent[mx];

void makeSet(int u) { parent[u] = u; }

void init(int n) {
  for (int i = 0; i <= n; i++)
    makeSet(i);
}

int Find(int u) {
  if (u == parent[u])
    return u;

  return parent[u] = Find(parent[u]);
}

void Union(int u, int v) {
  int p = Find(u);
  int q = Find(v);

  if (p != q) {
    parent[q] = p;
  }
}

int main() {
  int n, m, u, v, w;
  sii(n, m);

  init(n);

  Rep(i, 1, m) {
    sii(u, v);

    Union(u, v);
  }

  set<int> st;

  Rep(i, 1, n) { st.insert(Find(i)); }

  cout << st.size() - 1;

  return 0;
}


#include <bits/stdc++.h>
using namespace std;
using ll = long long;
vector<vector<int>> g;
vector<int> a;
int ans;
void dfs(int x, int prev) {
  for (auto i : g[x])
    if (i != prev) {
      ans++;
      dfs(i, x);
    }
}
int main() {
  int n, m;
  cin >> n >> m;
  g.resize(n);
  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    u--, v--;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  dfs(0, -1);
  cout << ans << endl;
}

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
  int n, m;
  cin >> n >> m;
  vector<vector<int>> g(n);
  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    u--, v--;
    g[u].push_back(v);
    g[v].push_back(u);
  }
  priority_queue<int> q;
  vector<int> a;
  vector<int> vis(n, 0);
  for (int i = 0; i < n; i++) {
    if (vis[i] == 0) {
      q.push(i);
      vis[i] = 1;
      while (!q.empty()) {
        int x = q.top();
        q.pop();
        a.push_back(x);
        for (auto j : g[x])
          if (vis[j] == 0) {
            vis[j] = 1;
            q.push(j);
          }
      }
      a.push_back(-1);
    }
  }
  int ans = 0;
  for (auto i : a)
    if (i == -1)
      ans++;
  cout << max(ans - 1, 0) << endl;
}


#include <iostream>
#include <vector>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define rrep(i, n) for (int i = 1; i <= (n); i++)
using namespace std;
using ll = long long;
using P = pair<int, int>;

// UnionFind
class UnionFind {
  vector<int> d;

public:
  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() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    --a;
    --b;
    uf.unite(a, b);
  }
  int ans = n - uf.size(n);
  cout << ans << endl;

  return 0;
}

#include <iostream>
#include <vector>
#define rep(i, n) for (int i = 0; i < (n); i++)
#define rrep(i, n) for (int i = 1; i <= (n); i++)
using namespace std;
using ll = long long;
using P = pair<int, int>;

// UnionFind
class UnionFind {
public:
  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() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    --a;
    --b;
    uf.unite(a, b);
  }
  int ans = n;
  int k = 0;
  rep(i, n) {
    if (uf.d[i] < 0) {
      ans += uf.d[i];
      ++k;
    }
  }
  --ans;
  ++k;
  ans += k - 1;
  cout << ans << endl;

  return 0;
}


#include <bits/stdc++.h>

using namespace std;

#define rep(i, n) for (int i = 0; i < n; i++)
#define P pair<int, int>
#define mkp make_pair

struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

int main() {
  int N, M;
  cin >> N >> M;
  dsu d(N);
  rep(i, M) {
    int a, b;
    cin >> a >> b;
    d.merge(a, b);
  }

  set<int> s;
  rep(i, N) { s.insert(d.leader(i)); }
  cout << s.size() - 1 << endl;
}

#include <bits/stdc++.h>

using namespace std;

#define rep(i, n) for (int i = 0; i < n; i++)
#define P pair<int, int>
#define mkp make_pair

struct dsu {
public:
  dsu() : _n(0) {}
  dsu(int n) : _n(n), parent_or_size(n, -1) {}

  int merge(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    int x = leader(a), y = leader(b);
    if (x == y)
      return x;
    if (-parent_or_size[x] < -parent_or_size[y])
      std::swap(x, y);
    parent_or_size[x] += parent_or_size[y];
    parent_or_size[y] = x;
    return x;
  }

  bool same(int a, int b) {
    assert(0 <= a && a < _n);
    assert(0 <= b && b < _n);
    return leader(a) == leader(b);
  }

  int leader(int a) {
    assert(0 <= a && a < _n);
    if (parent_or_size[a] < 0)
      return a;
    return parent_or_size[a] = leader(parent_or_size[a]);
  }

  int size(int a) {
    assert(0 <= a && a < _n);
    return -parent_or_size[leader(a)];
  }

  std::vector<std::vector<int>> groups() {
    std::vector<int> leader_buf(_n), group_size(_n);
    for (int i = 0; i < _n; i++) {
      leader_buf[i] = leader(i);
      group_size[leader_buf[i]]++;
    }
    std::vector<std::vector<int>> result(_n);
    for (int i = 0; i < _n; i++) {
      result[i].reserve(group_size[i]);
    }
    for (int i = 0; i < _n; i++) {
      result[leader_buf[i]].push_back(i);
    }
    result.erase(
        std::remove_if(result.begin(), result.end(),
                       [&](const std::vector<int> &v) { return v.empty(); }),
        result.end());
    return result;
  }

private:
  int _n;
  // root node: -1 * component size
  // otherwise: parent
  std::vector<int> parent_or_size;
};

int main() {
  int N, M;
  cin >> N >> M;
  dsu d(N);
  rep(i, M) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    d.merge(a, b);
  }

  set<int> s;
  rep(i, N) { s.insert(d.leader(i)); }
  cout << s.size() - 1 << endl;
}


#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define REP(i, n) for (int i = 0; i < n; ++i)
#define rREP(i, n) for (int i = n - 1; i >= 0; --i)
#define FOR(i, a, b) for (int i = a; i <= b; ++i)
#define rFOR(i, a, b) for (int i = a; i >= b; --i)
#define aFOR(i, V) for (auto i : V)
#define ALL(vec) vec.begin(), vec.end()
#define rALL(vec) vec.rbegin(), vec.rend()
#define INF 1'000'000'007              // 10^9 + 7
#define MOD 1'000'000'007              // 10^9 + 7
#define lINF 1'000'000'000'000'000'007 // 10^18 + 7
// ------------------------------

vector<int> group;
int num = 0;

void bfs(vector<vector<int>> vec, int now) {
  ++num;
  group[now] = num;
  queue<int> q;
  q.push(now);
  while (!q.empty()) {
    now = q.front();
    q.pop();
    group[now] = num;
    aFOR(i, vec[now]) {
      if (!group[i]) {
        q.push(i);
        group[i] = num;
      }
    }
  }
}

int main() {
  int N, M;
  cin >> N >> M;
  vector<vector<int>> vec(N, vector<int>(0));
  vector<int> tmp(N, 0);
  group = tmp;
  REP(i, M) {
    int A, B;
    cin >> A >> B;
    --A, --B;
    vec[A].push_back(B);
    vec[B].push_back(A);
  }
  int flag = true, now = 0;
  REP(i, N) if (!group[i]) {
    now = i;
    flag = true;
    bfs(vec, now);
  }
  cout << num - 1 << endl;
  return 0;
}

#include <atcoder/dsu>
#include <bits/stdc++.h>
using namespace std;
using namespace atcoder;
using ll = long long;
#define REP(i, n) for (int i = 0; i < n; ++i)
#define rREP(i, n) for (int i = n - 1; i >= 0; --i)
#define FOR(i, a, b) for (int i = a; i <= b; ++i)
#define rFOR(i, a, b) for (int i = a; i >= b; --i)
#define aFOR(i, V) for (auto i : V)
#define ALL(vec) vec.begin(), vec.end()
#define rALL(vec) vec.rbegin(), vec.rend()
#define INF 1'000'000'007              // 10^9 + 7
#define MOD 1'000'000'007              // 10^9 + 7
#define lINF 1'000'000'000'000'000'007 // 10^18 + 7
// ------------------------------

vector<int> group;
int num = 0;

void bfs(vector<vector<int>> vec, int now) {
  ++num;
  group[now] = num;
  queue<int> q;
  q.push(now);
  while (!q.empty()) {
    now = q.front();
    q.pop();
    group[now] = num;
    aFOR(i, vec[now]) {
      if (!group[i]) {
        q.push(i);
        group[i] = num;
      }
    }
  }
}

int main() {
  int N, M;
  cin >> N >> M;
  vector<vector<int>> vec(N, vector<int>(0));
  vector<int> tmp(N, 0);
  group = tmp;
  dsu d(N);
  REP(i, M) {
    int A, B;
    cin >> A >> B;
    --A, --B;
    d.merge(A, B);
  }
  set<int> s;
  REP(i, N) { s.insert(d.leader(i)); }
  cout << s.size() - 1 << endl;
  return 0;
}


#include <algorithm>
#include <cmath>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <stdio.h>
#include <string>
#include <tuple>
#include <vector>
#define rep(i, n) for (int i = 0; i < (int)n; i++) // n回繰り返す
#define rrep(i, n) for (int i = n - 1; i >= 0; i--)
#define REP(i, n) for (int i = 1; i <= (int)n; i++) // i=1からi=nまで
#define RREP(i, n) for (int i = n; i >= 1; i--)
using namespace std;
using ll = long long;
using P = pair<int, int>;
using graph = vector<vector<int>>;
const ll mod = pow(10, 9) + 7;
const int INF = 1e9;
const ll LINF = 1e18;
const double pi = 3.14159265358979323846;
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return 1;
  }
  return 0;
}

int main() {
  int N, M;
  cin >> N >> M;
  graph r(N + 1);
  rep(i, M) {
    int A, B;
    cin >> A >> B;

    r[A].push_back(B);

    r[B].push_back(A);
  }
  vector<bool> rooted(N + 1, false);
  int ans = 0;
  REP(i, N) {
    if (rooted[i] == false) {
      rooted[i] = true;
      queue<int> q;
      for (int &e : r[i]) {
        q.push(e);
      }
      while (!q.empty()) {
        int a = q.front();
        rooted[a] = true;
        q.pop();
        for (int &e : r[a]) {
          q.push(e);
        }
      }
      ans++;
    }
  }

  cout << ans - 1 << endl;
}

#include <algorithm>
#include <cmath>
#include <functional>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <stdio.h>
#include <string>
#include <tuple>
#include <vector>
#define rep(i, n) for (int i = 0; i < (int)n; i++) // n回繰り返す
#define rrep(i, n) for (int i = n - 1; i >= 0; i--)
#define REP(i, n) for (int i = 1; i <= (int)n; i++) // i=1からi=nまで
#define RREP(i, n) for (int i = n; i >= 1; i--)
using namespace std;
using ll = long long;
using P = pair<int, int>;
using graph = vector<vector<int>>;
const ll mod = pow(10, 9) + 7;
const int INF = 1e9;
const ll LINF = 1e18;
const double pi = 3.14159265358979323846;
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return 1;
  }
  return 0;
}

int main() {
  int N, M;
  cin >> N >> M;
  graph r(N + 1);
  rep(i, M) {
    int A, B;
    cin >> A >> B;

    r[A].push_back(B);

    r[B].push_back(A);
  }
  vector<bool> rooted(N + 1, false);
  int ans = 0;
  REP(i, N) {
    if (rooted[i] == false) {
      rooted[i] = true;
      queue<int> q;
      for (int &e : r[i]) {
        q.push(e);
      }
      while (!q.empty()) {
        int a = q.front();
        rooted[a] = true;
        q.pop();
        for (int &e : r[a]) {
          if (rooted[e] == false)
            q.push(e);
        }
      }
      ans++;
    }
  }

  cout << ans - 1 << endl;
}


// Generated by 1.1.7.1 https://github.com/kyuridenamida/atcoder-tools

#include <bits/stdc++.h>
// #include "atcoder/all"

using namespace std;

using i64 = long long;

const i64 MOD = 1e9 + 7;
const i64 INF = i64(1e18) + 7;

template <typename T> bool chmin(T &x, T y) {
  if (x > y) {
    x = y;
    return true;
  }
  return false;
}

template <typename T> bool chmax(T &x, T y) {
  if (x < y) {
    x = y;
    return true;
  }
  return false;
}

// doc:
// https://shibh308.github.io/library/library/lib/classes/unionfind.cpp.html
struct UnionFind {
  vector<int> par;
  int count;
  UnionFind(int n) : par(n, -1), count(0) {}
  int Find(int x) { return par[x] < 0 ? x : Find(par[x]); }
  int Size(int x) { return par[x] < 0 ? -par[x] : Size(par[x]); }
  bool Unite(int x, int y) {
    x = Find(x);
    y = Find(y);
    if (x == y)
      return false;
    if (par[x] > par[y])
      swap(x, y);
    par[x] += par[y];
    par[y] = x;
    return ++count;
  }
};

void solve(long long n, long long m, std::vector<long long> a,
           std::vector<long long> b) {
  UnionFind uf(n);
  for (int i = 0; i < n; ++i)
    uf.Unite(a[i] - 1, b[i] - 1);
  cout << n - uf.count - 1 << endl;
}

signed main() {
  long long N;
  scanf("%lld", &N);
  long long M;
  scanf("%lld", &M);
  std::vector<long long> A(M);
  std::vector<long long> B(M);
  for (int i = 0; i < M; i++) {
    scanf("%lld", &A[i]);
    scanf("%lld", &B[i]);
  }
  solve(N, M, std::move(A), std::move(B));
  return 0;
}

// Generated by 1.1.7.1 https://github.com/kyuridenamida/atcoder-tools

#include <bits/stdc++.h>
// #include "atcoder/all"

using namespace std;

using i64 = long long;

const i64 MOD = 1e9 + 7;
const i64 INF = i64(1e18) + 7;

template <typename T> bool chmin(T &x, T y) {
  if (x > y) {
    x = y;
    return true;
  }
  return false;
}

template <typename T> bool chmax(T &x, T y) {
  if (x < y) {
    x = y;
    return true;
  }
  return false;
}

// doc:
// https://shibh308.github.io/library/library/lib/classes/unionfind.cpp.html
struct UnionFind {
  vector<int> par;
  int count;
  UnionFind(int n) : par(n, -1), count(0) {}
  int Find(int x) { return par[x] < 0 ? x : Find(par[x]); }
  int Size(int x) { return par[x] < 0 ? -par[x] : Size(par[x]); }
  bool Unite(int x, int y) {
    x = Find(x);
    y = Find(y);
    if (x == y)
      return false;
    if (par[x] > par[y])
      swap(x, y);
    par[x] += par[y];
    par[y] = x;
    return ++count;
  }
};

void solve(long long n, long long m, std::vector<long long> a,
           std::vector<long long> b) {
  UnionFind uf(n);
  for (int i = 0; i < m; ++i)
    uf.Unite(a[i] - 1, b[i] - 1);
  cout << n - uf.count - 1 << endl;
}

signed main() {
  long long N;
  scanf("%lld", &N);
  long long M;
  scanf("%lld", &M);
  std::vector<long long> A(M);
  std::vector<long long> B(M);
  for (int i = 0; i < M; i++) {
    scanf("%lld", &A[i]);
    scanf("%lld", &B[i]);
  }
  solve(N, M, std::move(A), std::move(B));
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
struct Edge {
  int u, v;
} a[100005];
int n, m;

int fa[100005];

void init() {
  for (int i = 1; i <= n; i++) {
    fa[i] = i;
  }
}

int get(int x) {
  if (fa[x] == x) {
    return x;
  }
  return fa[x] = get(fa[x]);
}

int main() {
  scanf("%d %d", &n, &m);
  init();
  for (int i = 1; i <= m; i++) {
    scanf("%d %d %d", &a[i].u, &a[i].v);
    int x = get(a[i].u);
    int y = get(a[i].v);
    if (x != y) {
      fa[y] = x;
    }
  }
  int cnt = 0;
  for (int i = 1; i <= n; i++) {
    if (fa[i] == i) {
      cnt++;
    }
  }
  printf("%d", cnt - 1);
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
int u, v;
int n, m;

int fa[100005];

void init() {
  for (int i = 1; i <= n; i++) {
    fa[i] = i;
  }
}

int get(int x) {
  if (fa[x] == x) {
    return x;
  }
  return fa[x] = get(fa[x]);
}

int main() {
  scanf("%d %d", &n, &m);
  init();
  for (int i = 1; i <= m; i++) {
    scanf("%d %d", &u, &v);
    int x = get(u);
    int y = get(v);
    if (x != y) {
      fa[y] = x;
    }
  }
  int cnt = 0;
  for (int i = 1; i <= n; i++) {
    if (fa[i] == i) {
      cnt++;
    }
  }
  printf("%d", cnt - 1);
  return 0;
}


#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

using namespace std;

using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;

#define BIL ((ll)1e9)
#define MOD ((ll)1e9 + 7)
#define INF (1LL << 60) // 1LL<<63でオーバーフロー
#define inf (1 << 29)   // 1<<29でオーバーフロー

struct UnionFind {
  vector<int> par;

  UnionFind(int n) { par = vector<int>(n, -1); }
  int root(int x) {
    if (par[x] < 0)
      return x;
    return par[x] = root(par[x]);
  }
  bool merge(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return false;
    if (par[rx] > par[ry])
      swap(x, y);
    par[rx] += par[ry];
    par[ry] = rx;
    return true;
  }
  bool isSame(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
  int size(int x) { return -par[root(x)]; }
};

int main(int argc, char *argv[]) {
  cin.tie(0);
  ios::sync_with_stdio(0);
  cout << fixed << setprecision(20);

  int n, m;
  cin >> n >> m;

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

  set<int> check;
  for (int i = 0; i < n; i++) {
    int j = city.root(i);
    check.insert(j);
  }

  cout << check.size() - 1 << endl;

  return 0;
}

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

using namespace std;

using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;

#define BIL ((ll)1e9)
#define MOD ((ll)1e9 + 7)
#define INF (1LL << 60) // 1LL<<63でオーバーフロー
#define inf (1 << 29)   // 1<<29でオーバーフロー

struct UnionFind {
  vector<int> par;

  UnionFind(int n) {
    for (int i = 0; i < n; i++) {
      par.push_back(i);
    }
  }
  int root(int x) {
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }
  bool merge(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    if (rx == ry)
      return false;
    // if(par[rx] > par[ry]) swap(x, y);
    // par[rx] += par[ry];
    par[ry] = rx;
    return true;
  }
  bool isSame(int x, int y) {
    int rx = root(x);
    int ry = root(y);
    return rx == ry;
  }
  // int size(int x){
  //     return -par[root(x)];
  // }
};

int main(int argc, char *argv[]) {
  cin.tie(0);
  ios::sync_with_stdio(0);
  cout << fixed << setprecision(20);

  int n, m;
  cin >> n >> m;

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

  set<int> check;
  for (int i = 0; i < n; i++) {
    int j = city.root(i);
    check.insert(j);
  }

  cout << check.size() - 1 << endl;

  return 0;
}


#include <algorithm>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <string>
using namespace std;

const int maxn = 4e6 + 10;
vector<int> G[maxn];
bool vis[maxn];
void dfs(int u, int fa) {
  vis[u] = true;
  for (int i = 0; i < G[u].size(); i++) {
    int v = G[u][i];
    if (v != fa) {
      dfs(v, u);
    }
  }
}
void solved() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    G[u].push_back(v);
    G[v].push_back(u);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (!vis[i])
      dfs(i, -1), ans++;
  }
  cout << ans - 1 << endl;
}
int main() {
  solved();
  return 0;
}

#include <algorithm>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <string>
using namespace std;

const int maxn = 4e6 + 10;
vector<int> G[maxn];
bool vis[maxn];
void dfs(int u, int fa) {
  vis[u] = true;
  for (int i = 0; i < G[u].size(); i++) {
    int v = G[u][i];
    if (v != fa && !vis[v]) {
      dfs(v, u);
    }
  }
}
void solved() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    G[u].push_back(v);
    G[v].push_back(u);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (!vis[i])
      dfs(i, -1), ans++;
  }
  cout << ans - 1 << endl;
}
int main() {
  solved();
  return 0;
}

/tmp/698db5b4-43f2-4c19-ad5c-7cb05fa974b7.out: error while loading shared libraries: libc.so.6: failed to map segment from shared object


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(), (x).end()
#define make_unique(x)                                                         \\
  sort(all((x)));                                                              \\
  (x).resize(unique(all((x))) - (x).begin())
#define trace(...) __f(#__VA_ARGS__, __VA_ARGS__)
template <typename Arg1> void __f(const char *name, Arg1 &&arg1) {
  cerr << name << " : " << arg1 << '\\n';
}
template <typename Arg1, typename... Args>
void __f(const char *names, Arg1 &&arg1, Args &&...args) {
  const char *comma = strchr(names + 1, ',');
  cerr.write(names, comma - names) << " = " << arg1 << " | ";
  __f(comma + 1, args...);
}

ll max(ll a, ll b) { return (a > b ? a : b); }
ll min(ll a, ll b) { return (a < b ? a : b); }
ll dx[4] = {1, 0, -1, 0};
ll dy[4] = {0, 1, 0, -1};
void yes_or_no(ll x) {
  if (x)
    cout << "YES\\n";
  else
    cout << "NO\\n";
}
// find_parent -> Returns representative of group
// unite -> merge two groups
struct dsu {
  vector<int> parent, sz;
  dsu(int n) {
    parent = vector<int>(n + 1);
    sz = vector<int>(n + 1);

    for (int i = 0; i <= n; i++)
      parent[i] = i, sz[i] = 1;
  }
  int find_parent(int u) {
    return (u == parent[u] ? u : find_parent(parent[u]));
  }
  void unite(int u, int v) {
    int a = find_parent(u), b = find_parent(v);
    if (sz[a] > sz[b])
      parent[b] = a, sz[a] += sz[b];
    else
      parent[a] = b, sz[b] += sz[a];
  }
};
void solve_test() {
  ll ans = 0;
  ll n, m;
  cin >> n >> m;
  dsu d(n);

  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    d.unite(u, v);
  }
  ans--;

  for (int i = 1; i <= n; i++)
    if (d.find_parent(i) == i)
      ans++;

  cout << ans;
}
int main() {
  cin.sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  ll t;
  // cin >> t; while (t--)
  solve_test();
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define all(x) (x).begin(), (x).end()
#define make_unique(x)                                                         \\
  sort(all((x)));                                                              \\
  (x).resize(unique(all((x))) - (x).begin())
#define trace(...) __f(#__VA_ARGS__, __VA_ARGS__)
template <typename Arg1> void __f(const char *name, Arg1 &&arg1) {
  cerr << name << " : " << arg1 << '\\n';
}
template <typename Arg1, typename... Args>
void __f(const char *names, Arg1 &&arg1, Args &&...args) {
  const char *comma = strchr(names + 1, ',');
  cerr.write(names, comma - names) << " = " << arg1 << " | ";
  __f(comma + 1, args...);
}

ll max(ll a, ll b) { return (a > b ? a : b); }
ll min(ll a, ll b) { return (a < b ? a : b); }
ll dx[4] = {1, 0, -1, 0};
ll dy[4] = {0, 1, 0, -1};
void yes_or_no(ll x) {
  if (x)
    cout << "YES\\n";
  else
    cout << "NO\\n";
}

int n, edges;
vector<vector<int>> graph;
vector<int> vis;
void addEdge(int u, int v) {
  graph[u].push_back(v);
  graph[v].push_back(u);
}
void init() {
  graph = vector<vector<int>>(n + 1);
  vis = vector<int>(n + 1, 0);
}
void input() {
  cin >> n >> edges;
  init();
  int u, v;
  for (int i = 0; i < edges; i++) {
    cin >> u >> v;
    addEdge(u, v);
  }
}
void dfs(int u) {
  vis[u] = 1;

  for (int i : graph[u])
    if (vis[i] == 0)
      dfs(i);
}
void solve_test() {
  ll ans = 0;
  input();

  for (int i = 1; i <= n; i++)
    if (vis[i] == 0) {
      dfs(i);
      ans++;
    }
  ans--;
  cout << ans;
}
int main() {
  cin.sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  ll t;
  // cin >> t; while (t--)
  solve_test();
  return 0;
}


#include <bits/stdc++.h>
#include <iostream>
#define ll long long
using namespace std;
const ll num = 1e9 + 7;
ll mod(ll n) { return (n % num + num) % num; }
ll add(ll x, ll y) { return mod(mod(x) + mod(y)); }
ll mult(ll x, ll y) { return mod(mod(x) * mod(y)); }
ll sub(ll x, ll y) { return mod(mod(x) - mod(y)); }
ll pt(ll n, ll p) {
  if (p == 0)
    return 1;
  if (p == 1)
    return n % num;
  ll t = pt(n, p / 2);
  if (p % 2 == 0)
    return mult(t, t);
  else
    return mult(n, mult(t, t));
}
void dfs(vector<ll> &vt, vector<vector<ll>> &ad, ll i, ll pt) {
  vt[i] = 1;
  for (auto it : ad[i]) {
    if (it != pt && vt[it] == 0)
      dfs(vt, ad, it, i);
  }
}
void solve() {
  ll n, m;
  cin >> n >> m;
  vector<ll> vt(n + 1, 0);
  vector<vector<ll>> ad(n + 1, vector<ll>(n + 1));
  for (ll i = 0; i < m; i++) {
    ll x, y;
    cin >> x >> y;
    ad[x].push_back(y);
    ad[y].push_back(x);
  }
  ll ans = 0;
  for (ll i = 1; i <= n; i++) {
    if (vt[i] == 0) {
      dfs(vt, ad, i, -1);
      ans++;
    }
  }
  // dfs(vt,ad,1,-1);

  cout << ans - 1 << endl;
}
int main() {
  ll t;
  t = 1;
  while (t--) {
    solve();
  }
}

#include <bits/stdc++.h>
#include <iostream>
#define ll long long
using namespace std;
const ll num = 1e9 + 7;
ll mod(ll n) { return (n % num + num) % num; }
ll add(ll x, ll y) { return mod(mod(x) + mod(y)); }
ll mult(ll x, ll y) { return mod(mod(x) * mod(y)); }
ll sub(ll x, ll y) { return mod(mod(x) - mod(y)); }
ll pt(ll n, ll p) {
  if (p == 0)
    return 1;
  if (p == 1)
    return n % num;
  ll t = pt(n, p / 2);
  if (p % 2 == 0)
    return mult(t, t);
  else
    return mult(n, mult(t, t));
}
const int num1 = 100001;
vector<ll> ad[num1];
ll vt[100001];
void dfs(ll i, ll pt) {
  vt[i] = 1;
  for (auto it : ad[i]) {
    if (it != pt && vt[it] == 0)
      dfs(it, i);
  }
}

void solve() {
  ll n, m;
  cin >> n >> m;
  memset(vt, 0, sizeof(vt));
  for (ll i = 0; i < m; i++) {
    ll x, y;
    cin >> x >> y;
    ad[x].push_back(y);
    ad[y].push_back(x);
  }
  ll ans = 0;
  for (ll i = 1; i <= n; i++) {
    if (vt[i] == 0) {
      dfs(i, -1);
      ans++;
    }
  }
  // dfs(vt,ad,1,-1);

  cout << ans - 1 << endl;
}
int main() {
  ll t;
  t = 1;
  while (t--) {
    solve();
  }
}


#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
int modP(int a, int b) {
  if (a == 1)
    return 1;
  if (b == 0)
    return 1;
  if (b % 2 == 0) {
    a *= a;
    a = a % mod;
    return modP(a, b / 2) % mod;
  }

  return a * modP(a, b - 1) % mod;
}
int add(int a, int b) { return (a + b) % mod; }
int inv(int a, int b) { return a * modP(b, mod - 2) % mod; }
int fact[(int)1e5];
int fac(int n) {
  if (n <= 1)
    return fact[n] = 1;
  if (fact[n] != 0)
    return fact[n];
  return fact[n] = n * fac(n - 1);
}
int Cnk(int n, int k) { return fac(n) / fac(k) / fac(n - k); }
vector<int> g[(int)2e5];
void dfs(int s, vector<bool> &vis, int p) {
  vis[s] = true;
  for (int i : g[s]) {
    if (i == p)
      continue;
    dfs(i, vis, s);
  }
}
signed main() {
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  int cnt = 0;
  vector<bool> vis(n, false);
  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      dfs(i, vis, -1);
      cnt++;
    }
  }
  cout << cnt - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
int modP(int a, int b) {
  if (a == 1)
    return 1;
  if (b == 0)
    return 1;
  if (b % 2 == 0) {
    a *= a;
    a = a % mod;
    return modP(a, b / 2) % mod;
  }

  return a * modP(a, b - 1) % mod;
}
int add(int a, int b) { return (a + b) % mod; }
int inv(int a, int b) { return a * modP(b, mod - 2) % mod; }
int fact[(int)1e5];
int fac(int n) {
  if (n <= 1)
    return fact[n] = 1;
  if (fact[n] != 0)
    return fact[n];
  return fact[n] = n * fac(n - 1);
}
int Cnk(int n, int k) { return fac(n) / fac(k) / fac(n - k); }
vector<int> g[(int)2e5];
void dfs(int s, vector<bool> &vis, int p) {
  vis[s] = true;
  for (int i : g[s]) {
    if (i == p || vis[i])
      continue;
    dfs(i, vis, s);
  }
}
signed main() {
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    g[a].push_back(b);
    g[b].push_back(a);
  }
  int cnt = 0;
  vector<bool> vis(n, false);
  for (int i = 0; i < n; i++) {
    if (!vis[i]) {
      dfs(i, vis, -1);
      cnt++;
    }
  }
  cout << cnt - 1 << endl;
}