#include <bits/stdc++.h>
#define ll long long
#pragma GCC optimize("Ofast")
#pragma GCC target("avx,avx2,fma")
#define PI 2 * acos(0)
#define ones(n) __builtin_popcount(n)
#define FAST ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define MAX 9000000
#define debug(x) cerr << #x << " = " << x << '\\n';
#define LOOP printf("LOOP!!")
#define READ freopen("input.txt", "r", stdin)
#define PRINT freopen("output.txt", "w", stdout);
#define print(a, b) printf("Case %d: %lld\\n", a, b)
#define all(x) (x).begin(), (x).end()
const ll MOD = 1e9 + 7;
using namespace std;
const int VAL = 20000000;
vector<int> v[20000];

bool vis[100000];

void dfs(int n) {

  vis[n] = true;

  for (int i = 0; i < v[n].size(); i++)
    if (!vis[v[n][i]])
      dfs(v[n][i]);
}

/************MAIN_FUNCTION**********/
int main(int argc, char *argv[]) {
  /// Disjoint set union
  int n, m, a, b;
  cin >> n >> m;

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

    cin >> a >> b;
    v[b].push_back(a);
    v[a].push_back(b);
  }
  int c = 0;
  for (int i = 0; i < n; i++)
    if (!vis[i])
      dfs(i), c++;

  cout << c - 1 << '\\n';
  return 0;
}

#include <bits/stdc++.h>
#define ll long long
#pragma GCC optimize("Ofast")
#pragma GCC target("avx,avx2,fma")
#define PI 2 * acos(0)
#define ones(n) __builtin_popcount(n)
#define FAST ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define MAX 9000000
#define debug(x) cerr << #x << " = " << x << '\\n';
#define LOOP printf("LOOP!!")
#define READ freopen("input.txt", "r", stdin)
#define PRINT freopen("output.txt", "w", stdout);
#define print(a, b) printf("Case %d: %lld\\n", a, b)
#define all(x) (x).begin(), (x).end()
const ll MOD = 1e9 + 7;
using namespace std;
const int VAL = 2e5 + 10;
vector<int> v[VAL];

bool vis[VAL];

void dfs(int n) {

  vis[n] = true;

  for (int i = 0; i < v[n].size(); i++) {
    int next = v[n][i];

    if (!vis[next])
      dfs(next);
  }
}

/************MAIN_FUNCTION**********/
int main(int argc, char *argv[]) {
  /// Disjoint set union
  int n, m, a, b;
  cin >> n >> m;

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

    cin >> a >> b;
    v[a].push_back(b);
    v[b].push_back(a);
  }
  int c = 0;
  for (int i = 1; i <= n; i++)
    if (!vis[i])
      dfs(i), c++;

  cout << c - 1 << '\\n';
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
// (setq-default c-basic-offset 2)

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

int main() {
  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);
  }

  set<int> st;
  for (int i = 0; i < n; ++i) {
    st.insert(uf.find(i));
  }

  cout << (st.size() - 1) << endl;
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
// (setq-default c-basic-offset 2)

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

int main() {
  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);
  }

  set<int> st;
  for (int i = 1; i <= n; ++i) {
    st.insert(uf.find(i));
  }

  cout << (st.size() - 1) << endl;
  return 0;
}


// #fuccccc
#include <bits/stdc++.h>
#define TASK ""
#define inpf freopen(TASK ".inp", "r", stdin)
#define outf freopen(TASK ".out", "w", stdout)
#define ll long long
#define db double
#define F first
#define S second
#define maxn 1000005
#define MP make_pair
#define PB push_back

// C++ implementation of the approach
#include <bits/stdc++.h>
using namespace std;

// Graph class represents a undirected graph
// using adjacency list representation
class Graph {
  // No. of vertices
  int V;

  // Pointer to an array containing adjacency lists
  list<int> *adj;

  // A function used by DFS
  void DFSUtil(int v, bool visited[]);

public:
  // Constructor
  Graph(int V);

  void addEdge(int v, int w);
  int NumberOfconnectedComponents();
};

// Function to return the number of
// connected components in an undirected graph
int Graph::NumberOfconnectedComponents() {
  bool *visited = new bool[V];
  int count = 0;
  for (int v = 0; v < V; v++)
    visited[v] = false;

  for (int v = 0; v < V; v++) {
    if (visited[v] == false) {
      DFSUtil(v, visited);
      count += 1;
    }
  }
  return count;
}

void Graph::DFSUtil(int v, bool visited[]) {
  visited[v] = true;
  list<int>::iterator i;

  for (i = adj[v].begin(); i != adj[v].end(); ++i)
    if (!visited[*i])
      DFSUtil(*i, visited);
}

Graph::Graph(int V) {
  this->V = V;
  adj = new list<int>[V];
}
void Graph::addEdge(int v, int w) {
  adj[v].push_back(w);
  adj[w].push_back(v);
}
int main() {
  ll n, m;
  cin >> n >> m;
  Graph g(n);
  for (int i = 1; i <= m; i++) {
    ll u, v;
    cin >> u >> v;
    g.addEdge(u, v);
    g.addEdge(v, u);
  }
  cout << g.NumberOfconnectedComponents() - 1;
  return 0;
}

// #fuccccc
#include <bits/stdc++.h>
#define TASK ""
#define inpf freopen(TASK ".inp", "r", stdin)
#define outf freopen(TASK ".out", "w", stdout)
#define ll long long
#define db double
#define F first
#define S second
#define maxn 1000005
#define MP make_pair
#define PB push_back
using namespace std;
int n, m, tr[maxn];
vector<int> a[maxn];
void dfs(int u) {
  tr[u] = 1;
  for (auto v : a[u]) {
    if (!tr[v])
      dfs(v);
  }
}
int main() {
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    a[u].PB(v);
    a[v].PB(u);
  }
  ll ans = 0;
  for (int i = 1; i <= n; i++) {
    if (tr[i] == 0)
      dfs(i), ans++;
  }
  cout << ans - 1;
}


#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <utility>
#include <vector>

using namespace std;
struct aaa {
  aaa() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(20);
  };
} aaa;
template <class T> ostream &operator<<(ostream &o, const vector<T> &v) {
  o << "{";
  for (int i = 0; i < (int)v.size(); i++)
    o << (i > 0 ? ", " : "") << v[i];
  o << "}";
  return o;
}
#define debug(v)                                                               \\
  { cerr << "\\033[1;36m[debug]\\033[m " << #v << " : " << v << endl; }

using int64 = long long;

void dfs(const vector<vector<int>> &g, const int &i, vector<bool> &visited) {
  if (visited[i])
    return;
  visited[i] = true;

  for (auto v : g[i]) {
    if (visited[v])
      continue;
    cerr << i << "=>" << v << endl;
    dfs(g, v, visited);
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  vector<vector<int>> g(n + 1);

  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    g[a].emplace_back(b);
    g[b].emplace_back(a);
  }

  int ans = 0;
  vector<bool> visited(n + 1, false);
  for (int i = 1; i <= n; i++) {
    debug(visited);
    if (visited[i])
      continue;
    dfs(g, i, visited);
    ans++;
  }

  cout << ans - 1 << endl;
}

#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <utility>
#include <vector>

using namespace std;
struct aaa {
  aaa() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(20);
  };
} aaa;
template <class T> ostream &operator<<(ostream &o, const vector<T> &v) {
  o << "{";
  for (int i = 0; i < (int)v.size(); i++)
    o << (i > 0 ? ", " : "") << v[i];
  o << "}";
  return o;
}
#define debug(v)                                                               \\
  { cerr << "\\033[1;36m[debug]\\033[m " << #v << " : " << v << endl; }

using int64 = long long;

void dfs(const vector<vector<int>> &g, const int &i, vector<bool> &visited) {
  if (visited[i])
    return;
  visited[i] = true;

  for (auto v : g[i]) {
    if (visited[v])
      continue;
    dfs(g, v, visited);
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  vector<vector<int>> g(n + 1);

  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    g[a].emplace_back(b);
    g[b].emplace_back(a);
  }

  int ans = 0;
  vector<bool> visited(n + 1, false);
  for (int i = 1; i <= n; i++) {
    if (visited[i])
      continue;
    dfs(g, i, visited);
    ans++;
  }

  cout << ans - 1 << endl;
}


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

vector<int> g[100001];
vector<bool> vis;

void dfs(int src) {
  vis[src] = true;
  for (auto child : g[src]) {
    if (!vis[child]) {
      dfs(child);
    }
  }
}

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

  for (int i = 1; i <= n; i++) {
    vis[i] = false;
  }

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

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

vector<int> g[100001];
bool vis[100001];

void dfs(int src) {
  vis[src] = true;
  for (auto child : g[src]) {
    if (!vis[child]) {
      dfs(child);
    }
  }
}

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

  for (int i = 1; i <= n; i++) {
    vis[i] = false;
  }

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


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

struct UnionFind {
  vector<int> d;
  UnionFind(int n) : 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);
  vector<int> A(N), B(N);
  for (int i = 0; i < M; i++) {
    cin >> A[i] >> B[i];
    A[i]--, B[i]--;
    uf.unite(A[i], B[i]);
  }
  int ans = N - 1;
  for (int i = 0; i < M; i++) {
    if (uf.same(A[i], B[i]))
      ans--;
  }
  cout << ans << endl;
  return 0;
}

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

struct UnionFind {
  vector<int> d;
  UnionFind(int n) : 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);

  int ans = N - 1;
  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    A--, B--;
    if (!uf.same(A, B)) {
      uf.unite(A, B);
      ans--;
    }
  }
  cout << ans << endl;
  return 0;
}


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

void DFS(vector<vector<bool>> friends, int n, vector<bool> &visited, int v) {
  for (int i = 0; i < n; ++i) {

    // A student is in the friend circle if he/she is friends with the student
    // represented by studentIndex and if he/she is not already in a friend
    // circle
    if (friends[v][i] == 1 && !visited[i] && i != v) {
      visited[i] = 1;
      DFS(friends, n, visited, i);
    }
  }
}

int friendCircles(vector<vector<bool>> friends, int n) {
  if (!n) {
    return 0;
  }

  int numCircles = 0; // Number of friend circles

  // Keep track of whether a student is already in a friend circle
  vector<bool> visited(n, 0);

  // Start with the first student and recursively find all other students in
  // his/her friend circle. Then, do the same thing for the next student that is
  // not already in a friend circle. Repeat until all students are in a friend
  // circle.
  for (int i = 0; i < n; ++i) {
    if (!visited[i]) {
      visited[i] = true;
      DFS(friends, n, visited, i); // Recursive step to find all friends
      numCircles++;
    }
  }

  return numCircles;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int n, m;
  cin >> n >> m;
  vector<vector<bool>> f(n + 1, vector<bool>(n, false));
  int a, b;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    f[a - 1][b - 1] = true;
    f[b - 1][a - 1] = true;
  }
  cout << friendCircles(f, n) - 1 << endl;
  return 0;
}

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

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

void dfs(int n) {
  vis[n] = 1;
  for (auto i : adj[n]) {
    if (vis[i] != 1)
      dfs(i);
  }
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int n, m;
  cin >> n >> m;
  adj.resize(n + 1, vector<int>());
  vis.resize(n + 1, 0);
  int a, b;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (vis[i] != 1) {
      ans++;
      dfs(i);
    }
  }
  cout << ans - 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;
const int MAX = 200010;
int par[MAX];
bool ck[MAX];

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

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;
  ck[y] = false;
  return;
}

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

  init();

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

  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;

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

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

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

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

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

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

  UnionFind u(n);

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

  vector<bool> ck(n, false);

  rep(i, 0, n) { ck[u.root(i)] = true; }

  int cnt = 0;

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

  cout << cnt - 1 << endl;

  return 0;
}


/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author Pine1999
 */

#include <bits/stdc++.h>
#include <cassert>
#include <vector>

class unionfind {
public:
  int _n;
  std::vector<int> d;

  unionfind(int n) : _n(n) { d.resize(n, -1); }

  int find(int x) {
    assert(0 <= x && x < _n);
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }

  void unite(int x, int y) {
    assert(0 <= x && x < _n);
    assert(0 <= y && y < _n);
    x = find(x);
    y = find(y);
    if (x == y)
      return;
    if (d[x] > d[y])
      std::swap(x, y);
    d[x] += y;
    y = d[x];
  }

  bool same(int x, int y) {
    assert(0 <= x && x < _n);
    assert(0 <= y && y < _n);
    return find(x) == find(y);
  }

  int size(int x) {
    assert(0 <= x && x < _n);
    return -d[x];
  }
};

using namespace std;

void CConnectCities(std::istream &in, std::ostream &out) {
  int n, m;
  in >> n >> m;
  int ans = n - 1;
  unionfind uf(n);
  for (int i = 0; i < m; ++i) {
    int a, b;
    in >> a >> b;
    --a;
    --b;
    if (!uf.same(a, b)) {
      uf.unite(a, b);
      --ans;
    }
  }
  out << ans << '\\n';
}

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

/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author Pine1999
 */

#include <bits/stdc++.h>
// use library with "../library/***.hpp"
#include <cassert>
#include <vector>

class unionfind {
public:
  int _n;
  std::vector<int> d;

  unionfind(int n) : _n(n) { d.resize(n, -1); }

  int find(int x) {
    assert(0 <= x && x < _n);
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }

  void unite(int x, int y) {
    assert(0 <= x && x < _n);
    assert(0 <= y && y < _n);
    x = find(x);
    y = find(y);
    if (x == y)
      return;
    if (d[x] > d[y])
      std::swap(x, y);
    d[x] += d[y];
    d[y] = x;
  }

  bool same(int x, int y) {
    assert(0 <= x && x < _n);
    assert(0 <= y && y < _n);
    return find(x) == find(y);
  }

  int size(int x) {
    assert(0 <= x && x < _n);
    return -d[x];
  }
};

using namespace std;

void CConnectCities(std::istream &in, std::ostream &out) {
  int n, m;
  in >> n >> m;
  int ans = n - 1;
  unionfind uf(n);
  for (int i = 0; i < m; ++i) {
    int a, b;
    in >> a >> b;
    --a;
    --b;
    if (!uf.same(a, b)) {
      uf.unite(a, b);
      --ans;
    }
  }
  out << ans << '\\n';
}

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


#include <bits/stdc++.h>
#define MOD (long long)(1E9 + 7)
#define rep(i, n) for (int i = 0; i < n; i++)

using namespace std;

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> a(m);
  vector<int> b(m);
  vector<int> data(n + 1);
  rep(i, m) {
    cin >> a[i] >> b[i];
    a[i] = (a[i] << 17) + b[i];
  }
  sort(a.begin(), a.end());
  rep(i, m) {
    b[i] = (a[i]) & ((1 << 17) - 1);
    a[i] >>= 17;
  }
  int count = 0;
  rep(i, m) {
    if (data[a[i]] == 0) {
      data[a[i]] = data[b[i]] = a[i];
      count++;
    } else {
      data[b[i]] = data[a[i]];
    }
  }
  rep(i, n) {
    if (data[i + 1] == 0)
      count++;
  }
  cout << count - 1 << endl;
}

#include <atcoder/dsu>
#include <bits/stdc++.h>

#define MOD (long long)(1E9 + 7)
#define rep(i, n) for (int i = 0; i < n; i++)
using namespace atcoder;
using namespace std;

int main() {
  int n, m;
  cin >> n >> m;
  int a, b;
  dsu d(n);
  vector<int> data(n + 1);
  rep(i, m) {
    cin >> a >> b;
    d.merge(a - 1, b - 1);
  }
  auto c = d.groups();
  cout << c.size() - 1 << endl;
}


#include <bits/stdc++.h>
#include <iostream>
using namespace std;
vector<vector<int>> adj;
int dfs(int u, int p) {
  int ans = 0;
  for (auto index : adj[u]) {
    if (index != p) {
      int doit = dfs(index, u);
      ans++;
      ans += doit;
    }
  }
  return ans;
}
int main() {
  int n, m;
  int count1 = 0;
  cin >> n >> m;
  adj.resize(n + 1);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  count1 = dfs(0, -1);
  count1++;
  cout << n - count1;
  adj.clear();
}

#include <bits/stdc++.h>
#include <iostream>
using namespace std;
vector<vector<int>> adj;
vector<bool> vis;
void dfs(int v) {
  vis[v] = true;
  for (auto it : adj[v]) {
    if (!vis[it])
      dfs(it);
  }
}
int main() {
  int n, m;
  int ans = 0;
  cin >> n >> m;
  adj.resize(n + 1);
  vis.resize(n + 1, false);
  set<int> s;
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }
  for (int i = 1; i <= n; i++) {
    if (!vis[i]) {
      dfs(i);
      ans++;
    }
  }
  cout << ans - 1;
}


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

int main() {
  long long n, m;
  cin >> n >> m;
  vector<vector<int>> michi(n, vector<int>(0));
  vector<int> checked(n, 0);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    michi.at(a).push_back(b);
    michi.at(b).push_back(a);
  }
  long long ans = 0;
  int y = 1;
  int l = 0;
  while (y != 0) {
    queue<int> q;
    q.push(l);
    checked.at(l) = 1;
    while (q.size()) {
      int x = q.front();
      q.pop();

      for (int i = 0; i < (int)michi.at(x).size(); i++) {
        int u = michi.at(x).at(i);
        if (checked.at(u) == 1) {
          continue;
        }
        checked.at(u) = 1;
        q.push(u);
      }
    }
    y = 0;
    for (int i = 0; i < n; i++) {
      if (checked.at(i) == 0) {
        l = i;
        y = 1;
        // cout << l << endl;
        ans++;
        break;
      }
    }
  }
  cout << ans << endl;
}

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

int main() {
  long long n, m;
  cin >> n >> m;
  vector<vector<int>> michi(n, vector<int>(0));
  vector<int> checked(n, 0);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    michi.at(a).push_back(b);
    michi.at(b).push_back(a);
  }
  long long ans = 0;
  int y = 1;
  int l = 0;
  while (y != 0) {
    queue<int> q;
    q.push(l);
    checked.at(l) = 1;
    while (q.size()) {
      int x = q.front();
      q.pop();

      for (int i = 0; i < (int)michi.at(x).size(); i++) {
        int u = michi.at(x).at(i);
        if (checked.at(u) == 1) {
          continue;
        }
        checked.at(u) = 1;
        q.push(u);
      }
    }
    y = 0;
    for (int i = l; i < n; i++) {
      if (checked.at(i) == 0) {
        l = i;
        y = 1;
        // cout << l << endl;
        ans++;
        break;
      }
    }
  }
  cout << ans << endl;
}


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

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

int main() {
  int N, M, count = 0;
  cin >> N >> M;
  UnionFind tree(N);
  int R[100000];

  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    tree.unite(A, B);
  }
  for (int i = 0; i < N; i++) {
    int x = tree.root(i);
    R[x]++;
  }
  for (int i = 0; i < N; i++) {
    if (R[i])
      count++;
  }
  cout << count - 1 << endl;
  return 0;
}

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

typedef long long ll;
typedef 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;
  }
} UnionFind;

int main() {
  int N, M;
  cin >> N >> M;
  UnionFind tree(N);
  for (int i = 0; i < M; i++) {
    int A, B;
    cin >> A >> B;
    --A;
    --B;
    tree.unite(A, B);
  }
  int cnt = 0;
  for (int i = 0; i < N; i++) {
    if (tree.par[i] == i)
      cnt++;
  }
  cout << cnt - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
#define ll long long int
#define all(x) x.begin(), x.end()
using namespace std;
ll power(ll x, ll y, ll m) {
  if (y == 0)
    return 1;
  ll p = power(x, y / 2, m) % m;
  p = (p * p) % m;

  return (y % 2 == 0) ? p : (x * p) % m;
}
ll modInverse(ll a, ll m) {
  ll g = __gcd(a, m);
  if (g != 1)
    cout << "Inverse doesn't exist";

  // If a and m are relatively prime, then modulo inverse
  // is a^(m-2) mode m

  return power(a, m - 2, m);
}

ll fact[1000];
void fac(ll m) {
  ll i;
  fact[0] = 1;
  for (i = 1; i < 1000; i++) {
    fact[i] = (i * fact[i - 1]) % m;
  }
}

ll comb(ll n, ll k, ll m) {
  return (fact[n] * modInverse(fact[k], m) % m * modInverse(fact[n - k], m) %
          m) %
         m;
}
bool cmp(pair<ll, ll> a, pair<ll, ll> b) {
  if (a.second == b.second)
    return a.first < b.first;
  return a.second > b.second;
}
void SieveOfEratosthenes(ll n) {
  // Create a boolean array "prime[0..n]" and initialize
  // all entries it as true. A value in prime[i] will
  // finally be false if i is Not a prime, else true.
  bool prime[n + 1];
  memset(prime, true, sizeof(prime));

  for (ll p = 2; p * p <= n; p++) {
    // If prime[p] is not changed, then it is a prime
    if (prime[p] == true) {
      // Update all multiples of p greater than or
      // equal to the square of it
      // numbers which are multiple of p and are
      // less than p^2 are already been marked.
      for (ll i = p * p; i <= n; i += p)
        prime[i] = false;
    }
  }
}
void dfs(ll i, vector<ll> &visited, vector<vector<ll>> adj) {
  visited[i] = 1;
  for (auto x : adj[i]) {
    if (!visited[x])
      dfs(x, visited, adj);
  }
}
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int t;
  // cin>>t;
  t = 1;

  while (t-- > 0) {
    ll n, m;
    cin >> n >> m;
    ll i;
    vector<vector<ll>> adj(n);
    vector<ll> visited(n, 0);

    for (i = 0; i < m; i++) {
      ll u, v;
      cin >> u >> v;
      u--;
      v--;
      adj[u].push_back(v);
      adj[v].push_back(u);
    }
    ll ans = 0;
    for (i = 0; i < n; i++) {
      if (!visited[i]) {

        dfs(0, visited, adj);
        ans++;
      }
    }
    cout << ans - 1;
  }
  return 0;
}

#include <bits/stdc++.h>
#define ll long long int
#define all(x) x.begin(), x.end()
using namespace std;
ll power(ll x, ll y, ll m) {
  if (y == 0)
    return 1;
  ll p = power(x, y / 2, m) % m;
  p = (p * p) % m;

  return (y % 2 == 0) ? p : (x * p) % m;
}
ll modInverse(ll a, ll m) {
  ll g = __gcd(a, m);
  if (g != 1)
    cout << "Inverse doesn't exist";

  // If a and m are relatively prime, then modulo inverse
  // is a^(m-2) mode m

  return power(a, m - 2, m);
}

ll fact[1000];
void fac(ll m) {
  ll i;
  fact[0] = 1;
  for (i = 1; i < 1000; i++) {
    fact[i] = (i * fact[i - 1]) % m;
  }
}

ll comb(ll n, ll k, ll m) {
  return (fact[n] * modInverse(fact[k], m) % m * modInverse(fact[n - k], m) %
          m) %
         m;
}
bool cmp(pair<ll, ll> a, pair<ll, ll> b) {
  if (a.second == b.second)
    return a.first < b.first;
  return a.second > b.second;
}
void SieveOfEratosthenes(ll n) {
  // Create a boolean array "prime[0..n]" and initialize
  // all entries it as true. A value in prime[i] will
  // finally be false if i is Not a prime, else true.
  bool prime[n + 1];
  memset(prime, true, sizeof(prime));

  for (ll p = 2; p * p <= n; p++) {
    // If prime[p] is not changed, then it is a prime
    if (prime[p] == true) {
      // Update all multiples of p greater than or
      // equal to the square of it
      // numbers which are multiple of p and are
      // less than p^2 are already been marked.
      for (ll i = p * p; i <= n; i += p)
        prime[i] = false;
    }
  }
}
void dfs(ll i, vector<ll> &visited, vector<vector<ll>> &adj) {
  visited[i] = 1;
  for (auto x : adj[i]) {
    if (!visited[x])
      dfs(x, visited, adj);
  }
}
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int t;
  // cin>>t;
  t = 1;

  while (t-- > 0) {
    ll n, m;
    cin >> n >> m;
    ll i;
    vector<vector<ll>> adj(n);
    vector<ll> visited(n, 0);

    for (i = 0; i < m; i++) {
      ll u, v;
      cin >> u >> v;
      u--;
      v--;
      adj[u].push_back(v);
      adj[v].push_back(u);
    }
    ll ans = 0;
    for (i = 0; i < n; i++) {
      if (!visited[i]) {

        dfs(i, visited, adj);
        ans++;
      }
    }
    cout << ans - 1;
  }
  return 0;
}


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

using namespace std;
using namespace __gnu_pbds;

#define ll long long
#define X first
#define Y second
#define all(x) x.begin(), x.end()
#define ordered_set                                                            \\
  tree<int, null_type, less<int>, rb_tree_tag,                                 \\
       tree_order_statistics_node_update>

#define sim template <class c
#define ris return *this
#define dor > debug &operator<<
#define eni(x)                                                                 \\
  sim > typename enable_if<sizeof dud<c>(0) x 1, debug &>::type operator<<(    \\
            c i) {
sim > struct rge {
  c b, e;
};
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c *x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifdef LOCAL
  ~debug() { cerr << endl; }
  eni(!=) cerr << boolalpha << i;
  ris;
} eni(==) ris << range(begin(i), end(i));
}
sim, class b dor(pair<b, c> d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
    *this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
  sim dor(const c &) { ris; }
#endif
}
;
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "

const int MOD = 998244353;
const int MX = (int)1e5 + 10;

void add_self(int &x, int y, int md = MOD) {
  x += y;
  if (x >= md)
    x -= md;
}
void sub_self(int &x, int y, int md = MOD) {
  x -= y;
  if (x < 0)
    x += md;
}
void mul_self(int &x, int y, int md = MOD) { x = x * 1LL * y % md; }
inline int mul(int x, int y, int md = MOD) { return x * 1LL * y % md; }
inline int add(int x, int y, int md = MOD) {
  x += y;
  if (x >= md)
    x -= md;
  return x;
}
inline int sub(int x, int y, int md = MOD) {
  x -= y;
  if (x < 0)
    x += md;
  return x;
}

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

vector<int> read(int n, int k) {
  vector<int> vec(n);
  for (int i = 0; i < k; ++i)
    cin >> vec[i];
  int a, b, c, d;
  cin >> a >> b >> c >> d;
  for (int i = k; i < n; ++i)
    vec[i] = (a * 1ll * vec[i - 2] + b * 1ll * vec[i - 1] + c) % d;
  return vec;
}

vector<int> g[MX], vis(MX);

void dfs(int x, int par = -1) {
  vis[x] = true;
  for (auto &y : g[x]) {
    if (y == par)
      continue;
    dfs(y, x);
  }
}

void solve() {
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; ++i) {
    int x, y;
    cin >> x >> y;
    --x, --y;
    g[x].push_back(y);
    g[y].push_back(x);
  }
  int comp = 0;
  for (int i = 0; i < n; ++i) {
    if (!vis[i]) {
      dfs(i);
      comp++;
    }
  }
  cout << comp - 1 << "\\n";
  return;
}

int main() {

  cin.tie(0);
  cout.tie(0);
  ios_base::sync_with_stdio(0);

  // freopen("input.in", "r", stdin);
  // freopen("output.out", "w", stdout);

  int t;
  t = 1;
  // cin >> t;
  // pre();
  for (int i = 1; i <= t; ++i) {
    // cout << "Case #" << i << ": ";
    solve();
    // cout << '\\n';
    // cout << endl;
  }

  return 0;
}

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

using namespace std;
using namespace __gnu_pbds;

#define ll long long
#define X first
#define Y second
#define all(x) x.begin(), x.end()
#define ordered_set                                                            \\
  tree<int, null_type, less<int>, rb_tree_tag,                                 \\
       tree_order_statistics_node_update>

#define sim template <class c
#define ris return *this
#define dor > debug &operator<<
#define eni(x)                                                                 \\
  sim > typename enable_if<sizeof dud<c>(0) x 1, debug &>::type operator<<(    \\
            c i) {
sim > struct rge {
  c b, e;
};
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c *x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifdef LOCAL
  ~debug() { cerr << endl; }
  eni(!=) cerr << boolalpha << i;
  ris;
} eni(==) ris << range(begin(i), end(i));
}
sim, class b dor(pair<b, c> d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
    *this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
  sim dor(const c &) { ris; }
#endif
}
;
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "

const int MOD = 998244353;
const int MX = (int)1e5 + 10;

void add_self(int &x, int y, int md = MOD) {
  x += y;
  if (x >= md)
    x -= md;
}
void sub_self(int &x, int y, int md = MOD) {
  x -= y;
  if (x < 0)
    x += md;
}
void mul_self(int &x, int y, int md = MOD) { x = x * 1LL * y % md; }
inline int mul(int x, int y, int md = MOD) { return x * 1LL * y % md; }
inline int add(int x, int y, int md = MOD) {
  x += y;
  if (x >= md)
    x -= md;
  return x;
}
inline int sub(int x, int y, int md = MOD) {
  x -= y;
  if (x < 0)
    x += md;
  return x;
}

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

vector<int> read(int n, int k) {
  vector<int> vec(n);
  for (int i = 0; i < k; ++i)
    cin >> vec[i];
  int a, b, c, d;
  cin >> a >> b >> c >> d;
  for (int i = k; i < n; ++i)
    vec[i] = (a * 1ll * vec[i - 2] + b * 1ll * vec[i - 1] + c) % d;
  return vec;
}

vector<int> g[MX], vis(MX);

void dfs(int x, int par = -1) {
  vis[x] = true;
  for (auto &y : g[x]) {
    if (y == par)
      continue;
    if (!vis[y])
      dfs(y, x);
  }
}

void solve() {
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; ++i) {
    int x, y;
    cin >> x >> y;
    --x, --y;
    g[x].push_back(y);
    g[y].push_back(x);
  }
  int comp = 0;
  for (int i = 0; i < n; ++i) {
    if (!vis[i]) {
      dfs(i);
      comp++;
    }
  }
  cout << comp - 1 << "\\n";
  return;
}

int main() {

  cin.tie(0);
  cout.tie(0);
  ios_base::sync_with_stdio(0);

  // freopen("input.in", "r", stdin);
  // freopen("output.out", "w", stdout);

  int t;
  t = 1;
  // cin >> t;
  // pre();
  for (int i = 1; i <= t; ++i) {
    // cout << "Case #" << i << ": ";
    solve();
    // cout << '\\n';
    // cout << endl;
  }

  return 0;
}


/* -> Written by <-
   -----------
  |K_A_Z_A_M_A|
  |___________|
  |    _    |
  |   (^_^)   |
  |  /( | )\\  |
  |____|_|____|
    H O A N G
*/

#define Task ""
#define F first
#define S second
#define push_back pb
#define bit(x, i) (x >> i) & 1
#define inf 1e9 + 7
#define INF 1e18 + 7
#define all(x) x.begin(), x.end()

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>

using namespace std;

typedef long long ll;

const int MOD = 998244353;
const double eps = 1e-6;
const int maxn = 1e5 + 5;

int n, m;
vector<int> a[maxn];
int par[maxn];

void dfs(int u) {
  for (auto v : a[u])
    if (v != par[u]) {
      par[v] = u;
      dfs(v);
    }
}

void Solve() {
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    a[u].pb(v);
    a[v].pb(u);
  }
  int cnt = 0;
  for (int i = 1; i <= n; i++)
    if (par[i] == 0) {
      cnt++;
      dfs(i);
    }
  cout << cnt - 1;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  if (fopen(Task ".inp", "r")) {
    freopen(Task ".inp", "r", stdin);
    freopen(Task ".out", "w", stdout);
  }
  int test_case = 1;
  //    cin >> test_case;
  while (test_case--)
    Solve();
  return 0;
}

/* -> Written by <-
   -----------
  |K_A_Z_A_M_A|
  |___________|
  |    _    |
  |   (^_^)   |
  |  /( | )\\  |
  |____|_|____|
    H O A N G
*/

#define Task ""
#define F first
#define S second
#define push_back pb
#define bit(x, i) (x >> i) & 1
#define inf 1e9 + 7
#define INF 1e18 + 7
#define all(x) x.begin(), x.end()

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <unordered_map>
#include <vector>

using namespace std;

typedef long long ll;

const int MOD = 998244353;
const double eps = 1e-6;
const int maxn = 1e5 + 5;

int n, m;
vector<int> a[maxn];
int par[maxn];

void dfs(int u) {
  for (auto v : a[u])
    if (v != par[u]) {
      if (par[v])
        continue;
      par[v] = u;
      dfs(v);
    }
}

void Solve() {
  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    a[u].pb(v);
    a[v].pb(u);
  }
  int cnt = 0;
  for (int i = 1; i <= n; i++)
    if (par[i] == 0) {
      cnt++;
      dfs(i);
    }
  cout << cnt - 1;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  if (fopen(Task ".inp", "r")) {
    freopen(Task ".inp", "r", stdin);
    freopen(Task ".out", "w", stdout);
  }
  int test_case = 1;
  //    cin >> test_case;
  while (test_case--)
    Solve();
  return 0;
}


#include <bits/stdc++.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// using namespace __gnu_pbds;
/*---------------------DEBUGGING--------------------------------------------*/
void __print(int x) { cerr << x; }
void __print(long x) { cerr << x; }
void __print(long long x) { cerr << x; }
void __print(unsigned x) { cerr << x; }
void __print(unsigned long x) { cerr << x; }
void __print(unsigned long long x) { cerr << x; }
void __print(float x) { cerr << x; }
void __print(double x) { cerr << x; }
void __print(long double x) { cerr << x; }
void __print(char x) { cerr << '\\'' << x << '\\''; }
void __print(const char *x) { cerr << '\\"' << x << '\\"'; }
void __print(const string &x) { cerr << '\\"' << x << '\\"'; }
void __print(bool x) { cerr << (x ? "true" : "false"); }

template <typename T, typename V> void __print(const pair<T, V> &x) {
  cerr << '{';
  __print(x.first);
  cerr << ',';
  __print(x.second);
  cerr << '}';
}
template <typename T> void __print(const T &x) {
  int f = 0;
  cerr << '{';
  for (auto &i : x)
    cerr << (f++ ? "," : ""), __print(i);
  cerr << "}";
}
void _print() { cerr << "]\\n"; }
template <typename T, typename... V> void _print(T t, V... v) {
  __print(t);
  if (sizeof...(v))
    cerr << ", ";
  _print(v...);
}
#ifndef ONLINE_JUDGE
#define debug(x...)                                                            \\
  cerr << "[" << #x << "] = [";                                                \\
  _print(x)
#else
#define debug(x...)
#endif
/*-------------------------------------------------------------------------------------*/
// #define mp make_pair
#define pb push_back
#define ll long long
#define pii pair<int, int>
#define pcc pair<char, char>
#define F first
#define S second
#define int long long
#define pi 3.141592653589793238462643383279502
#define M 1000000007 // 998244353
#define rep(i, a, n) for (int i = a; i < n; i++)
#define INF 100000000000000000
#define N 200005
#define vi vector<int>
#define all(v) v.begin(), v.end()
#define endl "\\n"
// #define ordered_set tree<int, null_type,less<int>,
// rb_tree_tag,tree_order_statistics_node_update>
vector<int> adj[N];
int vis[N];
int dfs(int x) {
  vis[x] = 1;
  for (auto y : adj[x]) {
    if (!vis[y])
      dfs(y);
  }
}
signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  rep(i, 0, m) {
    int u, v;
    cin >> u >> v;
    adj[u].pb(v);
    adj[v].pb(u);
  }
  int cc = 0;
  for (int i = 1; i <= n; i++) {
    if (!vis[i]) {
      dfs(i);
      cc++;
    }
  }
  cout << (cc - 1) << endl;
}

#include <bits/stdc++.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
// #include <ext/pb_ds/assoc_container.hpp>
// #include <ext/pb_ds/tree_policy.hpp>
// using namespace __gnu_pbds;
/*---------------------DEBUGGING--------------------------------------------*/
void __print(int x) { cerr << x; }
void __print(long x) { cerr << x; }
void __print(long long x) { cerr << x; }
void __print(unsigned x) { cerr << x; }
void __print(unsigned long x) { cerr << x; }
void __print(unsigned long long x) { cerr << x; }
void __print(float x) { cerr << x; }
void __print(double x) { cerr << x; }
void __print(long double x) { cerr << x; }
void __print(char x) { cerr << '\\'' << x << '\\''; }
void __print(const char *x) { cerr << '\\"' << x << '\\"'; }
void __print(const string &x) { cerr << '\\"' << x << '\\"'; }
void __print(bool x) { cerr << (x ? "true" : "false"); }

template <typename T, typename V> void __print(const pair<T, V> &x) {
  cerr << '{';
  __print(x.first);
  cerr << ',';
  __print(x.second);
  cerr << '}';
}
template <typename T> void __print(const T &x) {
  int f = 0;
  cerr << '{';
  for (auto &i : x)
    cerr << (f++ ? "," : ""), __print(i);
  cerr << "}";
}
void _print() { cerr << "]\\n"; }
template <typename T, typename... V> void _print(T t, V... v) {
  __print(t);
  if (sizeof...(v))
    cerr << ", ";
  _print(v...);
}
#ifndef ONLINE_JUDGE
#define debug(x...)                                                            \\
  cerr << "[" << #x << "] = [";                                                \\
  _print(x)
#else
#define debug(x...)
#endif
/*-------------------------------------------------------------------------------------*/
// #define mp make_pair
#define pb push_back
#define ll long long
#define pii pair<int, int>
#define pcc pair<char, char>
#define F first
#define S second
#define int long long
#define pi 3.141592653589793238462643383279502
#define M 1000000007 // 998244353
#define rep(i, a, n) for (int i = a; i < n; i++)
#define INF 100000000000000000
#define N 200005
#define vi vector<int>
#define all(v) v.begin(), v.end()
#define endl "\\n"
// #define ordered_set tree<int, null_type,less<int>,
// rb_tree_tag,tree_order_statistics_node_update>
vector<int> adj[N];
int vis[N];
void dfs(int x) {
  vis[x] = 1;
  for (auto y : adj[x]) {
    if (!vis[y])
      dfs(y);
  }
}
signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  rep(i, 0, m) {
    int u, v;
    cin >> u >> v;
    adj[u].pb(v);
    adj[v].pb(u);
  }
  int cc = 0;
  for (int i = 1; i <= n; i++) {
    if (!vis[i]) {
      dfs(i);
      cc++;
    }
  }
  cout << (cc - 1) << endl;
}


#include <iostream>
using namespace std;
#define ll long long
#include <set>
#include <vector>
// vector<ll>adj[100001];
ll parent[100001];
ll tao[100001];
ll find(ll i) {
  if (parent[i] == i)
    return i;
  else
    return parent[i] = find(parent[i]);
}
ll union_set(ll a, ll b) {
  ll x = find(a);
  ll y = find(b);
  if (tao[x] < tao[y])
    swap(x, y);
  parent[y] = x;
  if (tao[x] == tao[y])
    tao[x]++;
}
set<ll> s;
int main() {
  ll n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    parent[i] = i;
    tao[i] = 1;
  }
  while (m--) {
    ll l, r;
    cin >> l >> r;
    union_set(l, r);
  }
  for (int i = 1; i <= n; i++)
    s.insert(find(i));
  cout << s.size() - 1 << endl;

  return 0;
}

#include <iostream>
using namespace std;
#define ll long long
#include <set>
#include <vector>
// vector<ll>adj[100001];
ll parent[100001];
ll tao[100001];
ll find(ll i) {
  if (parent[i] == i)
    return i;
  else
    return parent[i] = find(parent[i]);
}
void union_set(ll a, ll b) {
  ll x = find(a);
  ll y = find(b);
  if (tao[x] < tao[y])
    swap(x, y);
  parent[y] = x;
  if (tao[x] == tao[y])
    tao[x]++;
}
set<ll> s;
int main() {
  ll n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    parent[i] = i;
    tao[i] = 1;
  }
  while (m--) {
    ll l, r;
    cin >> l >> r;
    union_set(l, r);
  }
  for (int i = 1; i <= n; i++)
    s.insert(find(i));
  cout << s.size() - 1 << endl;

  return 0;
}


#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using P = pair<int, int>;

struct UnionFind {
  vector<int> d;
  UnionFind(int n = 0) : d(n, -1) {}
  int find(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }
  bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  bool same(int x, int y) { return find(x) == find(y); }
  int size(int x) { return -d[find(x)]; }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    --a;
    --b;
    uf.unite(a, b); // a & b has connection
  }
  set<int> checked;
  rep(j, n) {
    if (uf.same(0, j))
      checked.insert(j);
  }
  int ans = 0;
  for (int i = 1; i < n; ++i) {
    if (checked.count(i) == 0) {
      ++ans;
      rep(j, n) {
        if (uf.same(i, j))
          checked.insert(j);
      }
    }
  }
  cout << ans << endl;
  return 0;
}

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
using P = pair<int, int>;

struct UnionFind {
  vector<int> d;
  UnionFind(int n = 0) : d(n, -1) {}
  int find(int x) {
    if (d[x] < 0)
      return x;
    return d[x] = find(d[x]);
  }
  bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y)
      return false;
    if (d[x] > d[y])
      swap(x, y);
    d[x] += d[y];
    d[y] = x;
    return true;
  }
  bool same(int x, int y) { return find(x) == find(y); }
  int size(int x) { return -d[find(x)]; }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    --a;
    --b;
    uf.unite(a, b); // a & b has connection
  }
  int ans = 0;
  set<int> check;
  for (int id : uf.d) {
    if (id < 0)
      ++ans;
  }
  cout << ans - 1 << endl;
  return 0;
}


//_nebula_
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
int t;
const int M = 1e9 + 7;
const int inf = INT64_MAX;
int max(int a, int b, int c) { return max(a, max(b, c)); }
int min(int a, int b, int c) { return min(a, min(b, c)); }
int fspow(int x, int y) {
  int ans;
  if (y == 0)
    return 1;
  ans = fspow(x, y / 2);
  if (y % 2 == 0)
    return ((ans * ans) % M);
  return (x * ((ans * ans) % M)) % M;
}
int gcd(int x, int y) {
  if (x < y)
    swap(x, y);
  if (y == 0)
    return x;
  return gcd(x % y, y);
}
int p2(int x) {
  int ans = 0;
  while (x > 1) {
    ans++;
    x /= 2;
  }
  return ans;
}
vector<int> grp[10005];
bool vis[100005];
void dfs(int s) {
  if (vis[s])
    return;
  vis[s] = 1;
  for (auto i : grp[s])
    dfs(i);
}
signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  t = 1;
  // freopen("input.txt","r",stdin);
  // freopen("output.txt","w",stdout);
  //  cin>>t;
  // int tmp=t;
  while (t--) {
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
      int u, v;
      cin >> u >> v;
      grp[u].push_back(v);
      grp[v].push_back(u);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
      if (vis[i])
        continue;
      ans++;
      dfs(i);
    }
    cout << ans - 1 << "\\n";
  }
}

//_nebula_
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
int t;
const int M = 1e9 + 7;
const int inf = INT64_MAX;
int max(int a, int b, int c) { return max(a, max(b, c)); }
int min(int a, int b, int c) { return min(a, min(b, c)); }
int fspow(int x, int y) {
  int ans;
  if (y == 0)
    return 1;
  ans = fspow(x, y / 2);
  if (y % 2 == 0)
    return ((ans * ans) % M);
  return (x * ((ans * ans) % M)) % M;
}
int gcd(int x, int y) {
  if (x < y)
    swap(x, y);
  if (y == 0)
    return x;
  return gcd(x % y, y);
}
int p2(int x) {
  int ans = 0;
  while (x > 1) {
    ans++;
    x /= 2;
  }
  return ans;
}
vector<int> grp[100005];
bool vis[100005];
void dfs(int s) {
  if (vis[s])
    return;
  vis[s] = 1;
  for (auto i : grp[s])
    dfs(i);
}
signed main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  cout.tie(NULL);
  t = 1;
  // freopen("input.txt","r",stdin);
  // freopen("output.txt","w",stdout);
  //  cin>>t;
  // int tmp=t;
  while (t--) {
    int n, m;
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
      int u, v;
      cin >> u >> v;
      grp[u].push_back(v);
      grp[v].push_back(u);
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
      if (vis[i])
        continue;
      ans++;
      dfs(i);
    }
    cout << ans - 1 << "\\n";
  }
}


#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mod 1e9
#define vll vector<ll>
#define rep(i, a, b) for (ll i = a; i < b; i++)
#define repr(i, a, b) for (ll i = a; i >= b; i--)
#define repe(i, a) for (auto &i : a)
#define pb push_back
using namespace std;
void initialize(vector<int> &a, int n) {
  for (int i = 0; i < n; i++)
    a[i] = i;
}
int root(vector<int> Arr, int i) {
  while (Arr[i] != i) {
    i = Arr[i];
  }
  return i;
}
void g(vector<int> &Arr, int A, int B) {
  int root_A = root(Arr, A);
  int root_B = root(Arr, B);
  Arr[root_A] = root_B;
}
bool f(vector<int> a, int A, int B) {
  if (root(a, A) == root(a, B))
    return true;
  else
    return false;
}
int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(0);

  ll n, m;
  cin >> n >> m;
  vector<int> a(n);
  initialize(a, n);
  // for (int i = 0; i < n; i++)
  //   cout << a[i] << endl;

  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    g(a, u - 1, v - 1);
  }
  set<int> st;
  for (int i = 0; i < n; i++) {
    st.insert(root(a, i));
  }
  // for(int i=0;i<n;i++)cout<<a[i]<<endl;
  cout << st.size() - 1;

  return 0;
}

// C++ program to print connected components in
// an undirected graph
#include <iostream>
#include <list>
using namespace std;

// Graph class represents a undirected graph
// using adjacency list representation
int ans;
class Graph {
  int V; // No. of vertices

  // Pointer to an array containing adjacency lists
  list<int> *adj;

  // A function used by DFS
  void DFSUtil(int v, bool visited[]);

public:
  Graph(int V); // Constructor
  ~Graph();
  void addEdge(int v, int w);
  void connectedComponents();
};

// Method to print connected components in an
// undirected graph
void Graph::connectedComponents() {
  // Mark all the vertices as not visited
  bool *visited = new bool[V];
  for (int v = 0; v < V; v++)
    visited[v] = false;

  for (int v = 0; v < V; v++) {
    if (visited[v] == false) {
      // print all reachable vertices
      // from v
      DFSUtil(v, visited);

      // cout << "\\n";
      ans++;
    }
  }
  delete[] visited;
}

void Graph::DFSUtil(int v, bool visited[]) {
  // Mark the current node as visited and print it
  visited[v] = true;
  // cout << v << " ";

  // Recur for all the vertices
  // adjacent to this vertex
  list<int>::iterator i;
  for (i = adj[v].begin(); i != adj[v].end(); ++i)
    if (!visited[*i])
      DFSUtil(*i, visited);
}

Graph::Graph(int V) {
  this->V = V;
  adj = new list<int>[V];
}

Graph::~Graph() { delete[] adj; }

// method to add an undirected edge
void Graph::addEdge(int v, int w) {
  adj[v].push_back(w);
  adj[w].push_back(v);
}

// Driver program to test above
int main() {
  // Create a graph given in the above diagram
  // Graph g(5); // 5 vertices numbered from 0 to 4
  // g.addEdge(1, 0);
  // g.addEdge(2, 3);
  // g.addEdge(3, 4);

  // cout << "Following are connected components \\n";
  // g.connectedComponents();

  int n, m;
  cin >> n >> m;
  Graph g(n);
  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    g.addEdge(u - 1, v - 1);
  }
  ans = 0;
  g.connectedComponents();
  cout << ans - 1;

  return 0;
}


#define _GLIBCXX_DEBUG
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
using Graph = vector<vector<ll>>;

#define REP(i, n) for (ll i = 0; i < ll(n); i++)
#define REPD(i, n) for (ll i = n - 1; i >= 0; i--)
#define FOR(i, a, b) for (ll i = a; i < ll(b); i++)
#define FORD(i, a, b) for (ll i = a; i >= b; i--)
#define ALL(x) x.begin(), x.end()
#define SIZE(x) ll(x.size())

#define INF 1000000000000 // 10^12
#define MOD 1000000007    // 10^9+7:合同式の法

int main() {

  ll N, M;
  cin >> N >> M;

  Graph G(N);
  for (ll i = 0; i < M; ++i) {
    ll a, b;
    cin >> a >> b;
    G[a].push_back(b);
    G[b].push_back(a);
  }

  // BFS のためのデータ構造
  vector<ll> dist(N, -1); // 全頂点を「未訪問」に初期化
  // queue<ll> que;
  ll total = 0;

  // BFS 開始 (キューが空になるまで探索を行う)
  REP(i, N) {
    if (dist[i] == -1) {
      dist[i] = total;
      // cout << dist[i] << endl;
      total++;
      queue<ll> que;
      que.push(i);
      while (!que.empty()) {
        ll v = que.front(); // キューから先頭頂点を取り出す
        que.pop();

        // v から辿れる頂点をすべて調べる
        for (ll nv : G[v]) {
          if (dist[nv] != -1)
            continue; // すでに発見済みの頂点は探索しない

          // 新たな白色頂点 nv について距離情報を更新してキューに追加する
          dist[nv] = dist[v];
          que.push(nv);
        }
      }
    }
  }
  cout << total - 1 << endl;

  return 0;
}

#define _GLIBCXX_DEBUG
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
using Graph = vector<vector<ll>>;

#define REP(i, n) for (ll i = 0; i < ll(n); i++)
#define REPD(i, n) for (ll i = n - 1; i >= 0; i--)
#define FOR(i, a, b) for (ll i = a; i < ll(b); i++)
#define FORD(i, a, b) for (ll i = a; i >= b; i--)
#define ALL(x) x.begin(), x.end()
#define SIZE(x) ll(x.size())

#define INF 1000000000000 // 10^12
#define MOD 1000000007    // 10^9+7:合同式の法

int main() {

  ll N, M;
  cin >> N >> M;

  Graph G(N);
  for (ll i = 0; i < M; ++i) {
    ll a, b;
    cin >> a >> b;
    G[a - 1].push_back(b - 1);
    G[b - 1].push_back(a - 1);
  }

  // BFS のためのデータ構造
  vector<ll> dist(N, -1); // 全頂点を「未訪問」に初期化
  // queue<ll> que;
  ll total = 0;

  // BFS 開始 (キューが空になるまで探索を行う)
  REP(i, N) {
    if (dist[i] == -1) {
      dist[i] = total;
      // cout << dist[i] << endl;
      total++;
      queue<ll> que;
      que.push(i);
      while (!que.empty()) {
        ll v = que.front(); // キューから先頭頂点を取り出す
        que.pop();

        // v から辿れる頂点をすべて調べる
        for (ll nv : G[v]) {
          if (dist[nv] != -1)
            continue; // すでに発見済みの頂点は探索しない

          // 新たな白色頂点 nv について距離情報を更新してキューに追加する
          dist[nv] = dist[v];
          que.push(nv);
        }
      }
    }
  }
  cout << total - 1 << endl;

  return 0;
}


#include <iostream>
#include <queue>
#include <vector>

using namespace std;

int par(int i, vector<int> &group) {
  if (i == group[i])
    return i;
  else {
    return group[i] = par(group[i], group);
  }
}

bool same(int i, int j, vector<int> &group) {
  if (par(i, group) == par(j, group)) {
    return true;
  } else {
    return false;
  }
}

void unite(int i, int j, vector<int> &group, vector<int> &depth) {
  int a = par(i, group);
  int b = par(j, group);
  if (a != b) {
    if (depth[i] == depth[j]) {
      depth[i]++;
      depth[j] = depth[i];
      int p = j, p_g = group[j];
      while (true) {
        p = j;
        p_g = group[j];
        group[j] = a;
        if (p == p_g)
          break;
      }

    } else if (depth[i] > depth[j]) {
      depth[j] = depth[i];
      int p = j, p_g = group[j];
      while (true) {
        p = j;
        p_g = group[j];
        group[j] = a;
        if (p == p_g)
          break;
      }
    } else {
      depth[i] = depth[j];
      int p = i, p_g = group[i];
      while (true) {
        p = i;
        p_g = group[i];
        group[i] = b;
        if (p == p_g)
          break;
      }
    }
  }
}

int main(void) {

  int N, M;
  cin >> N >> M;
  vector<int> Par(N);
  vector<int> depth(N, 0);
  for (int i = 0; i < N; i++) {
    Par[i] = i;
  }
  int a, b;
  int c = 0;
  for (int i = 0; i < M; i++) {
    cin >> a >> b;

    if (!same(a - 1, b - 1, Par)) {
      unite(a - 1, b - 1, Par, depth);
      c++;
    }
  }
  // cout << N-1-c << endl;

  int count = 0;
  vector<int> counts(N, 0);
  for (int i = 0; i < N; i++) {
    int p = par(i, Par);
    if (counts[p] == 0) {
      counts[p]++;
      count++;
    }
  }
  cout << count - 1 << endl;

  return 0;
}

#include <iostream>
#include <queue>
#include <vector>

using namespace std;

int par(int i, vector<int> &group) {
  if (i == group[i])
    return i;
  else {
    return group[i] = par(group[i], group);
  }
}

bool same(int i, int j, vector<int> &group) {
  if (par(i, group) == par(j, group)) {
    return true;
  } else {
    return false;
  }
}

void unite(int i, int j, vector<int> &group, vector<int> &depth) {
  int a = par(i, group);
  int b = par(j, group);
  if (a != b) {
    if (depth[i] == depth[j]) {
      depth[i]++;
      group[b] = a;
    } else if (depth[i] > depth[j]) {
      group[b] = a;
    } else {
      group[a] = b;
    }
  }
}

int main(void) {

  int N, M;
  cin >> N >> M;
  vector<int> Par(N);
  vector<int> depth(N, 0);
  for (int i = 0; i < N; i++) {
    Par[i] = i;
  }
  int a, b;
  int c = 0;
  for (int i = 0; i < M; i++) {
    cin >> a >> b;

    if (!same(a - 1, b - 1, Par)) {
      unite(a - 1, b - 1, Par, depth);
      c++;
    }
  }
  // cout << N-1-c << endl;

  int count = 0;
  vector<int> counts(N, 0);
  for (int i = 0; i < N; i++) {
    int p = par(i, Par);
    if (counts[p] == 0) {
      counts[p]++;
      count++;
    }
  }
  cout << count - 1 << endl;

  return 0;
}


// vec[i]の範囲外エラー表示
#define _GLIBCXX_DEBUG
// includeとusing
#include <bits/stdc++.h>
using namespace std;
// 型名省略
template <class T> using V = vector<T>;
template <class T> using VV = V<V<T>>;
using ll = long long;
using st = string;
using vl = vector<long long>;
using vvl = vector<vector<long long>>;
using vc = vector<char>;
using vvc = vector<vector<char>>;
using vs = vector<string>;
using vb = vector<bool>;
using vvb = vector<vector<bool>>;
using pll = pair<ll, ll>;
using pcc = pair<char, char>;
// 定数
const ll INF = 1e9;
const ll MOD = 1e9 + 7;
const vl dx = {1, 0, -1, 0, 1, 0, -1, 0};
const vl dy = {0, 1, 0, -1, 0, 1, 0, -1};
// マクロ
#define rep(i, n) for (ll i = 0; i < (ll)n; i++)
#define rrep(i, n) for (ll i = (ll)n - 1; i >= 0; i--)
#define REP(i, x, n) for (ll i = (ll)x; i < (ll)n; i++)
#define RREP(i, x, n) for (ll i = (ll)n - 1; i >= x; i--)
#define each(i, A) for (ll i : (vl)A)
#define all(A) A.begin(), A.end()
#define Size(A) ll(A.size())
// 入力
template <typename T> T input() {
  T x;
  cin >> x;
  return x;
}
// 正誤判定
void Yes(bool ans) { cout << (ans ? "Yes" : "No") << endl; }
void YES(bool ans) { cout << (ans ? "YES" : "NO") << endl; }
// 余り切り上げ
ll ceil(ll a, ll b) { return (a + b - 1) / b; }
// 最大値・最小値
template <typename T> bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
template <typename T> bool chmin(T &a, const T &b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
// 最大公約数
ll gcd(ll a, ll b) {
  if (a < b)
    swap(a, b);
  if (a % b == 0)
    return b;
  return gcd(b, a % b);
}
// 最小公倍数
ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }
// n乗の余り
ll modpow(ll x, ll n, ll mod) {
  ll res = 1;
  for (ll i = 0; i < n; i++) {
    res = res * x % mod;
  }
  return res;
}
ll MODpow(ll x, ll n) {
  ll res = 1;
  for (ll i = 0; i < n; i++) {
    res = res * x % MOD;
  }
  return res;
}

/*--------------------------------------------------------------------------*/

vb seen;
void dfs(const vvl &G, ll v) {
  seen[v] = true;

  for (auto next_v : G[v]) {
    if (seen[next_v])
      continue;
    dfs(G, next_v);
  }
}

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

  vvl G(N);
  rep(i, M) {
    ll a, b;
    cin >> a >> b;
    G[a].push_back(b);
    G[b].push_back(a);
  }

  seen.assign(N, false);

  ll count = 0;
  rep(v, N) {
    if (seen[v])
      continue;
    ll start = v;
    dfs(G, start);
    count++;
  }

  cout << count - 1 << endl;
}

// vec[i]の範囲外エラー表示
#define _GLIBCXX_DEBUG
// includeとusing
#include <bits/stdc++.h>
using namespace std;
// 型名省略
template <class T> using V = vector<T>;
template <class T> using VV = V<V<T>>;
using ll = long long;
using st = string;
using vl = vector<long long>;
using vvl = vector<vector<long long>>;
using vc = vector<char>;
using vvc = vector<vector<char>>;
using vs = vector<string>;
using vb = vector<bool>;
using vvb = vector<vector<bool>>;
using pll = pair<ll, ll>;
using pcc = pair<char, char>;
// 定数
const ll INF = 1e9;
const ll MOD = 1e9 + 7;
const vl dx = {1, 0, -1, 0, 1, 0, -1, 0};
const vl dy = {0, 1, 0, -1, 0, 1, 0, -1};
// マクロ
#define rep(i, n) for (ll i = 0; i < (ll)n; i++)
#define rrep(i, n) for (ll i = (ll)n - 1; i >= 0; i--)
#define REP(i, x, n) for (ll i = (ll)x; i < (ll)n; i++)
#define RREP(i, x, n) for (ll i = (ll)n - 1; i >= x; i--)
#define each(i, A) for (ll i : (vl)A)
#define all(A) A.begin(), A.end()
#define Size(A) ll(A.size())
// 入力
template <typename T> T input() {
  T x;
  cin >> x;
  return x;
}
// 正誤判定
void Yes(bool ans) { cout << (ans ? "Yes" : "No") << endl; }
void YES(bool ans) { cout << (ans ? "YES" : "NO") << endl; }
// 余り切り上げ
ll ceil(ll a, ll b) { return (a + b - 1) / b; }
// 最大値・最小値
template <typename T> bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
template <typename T> bool chmin(T &a, const T &b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
// 最大公約数
ll gcd(ll a, ll b) {
  if (a < b)
    swap(a, b);
  if (a % b == 0)
    return b;
  return gcd(b, a % b);
}
// 最小公倍数
ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }
// n乗の余り
ll modpow(ll x, ll n, ll mod) {
  ll res = 1;
  for (ll i = 0; i < n; i++) {
    res = res * x % mod;
  }
  return res;
}
ll MODpow(ll x, ll n) {
  ll res = 1;
  for (ll i = 0; i < n; i++) {
    res = res * x % MOD;
  }
  return res;
}

/*--------------------------------------------------------------------------*/

vb seen;
void dfs(const vvl &G, ll v) {
  seen[v] = true;

  for (auto next_v : G[v]) {
    if (seen[next_v])
      continue;
    dfs(G, next_v);
  }
}

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

  vvl G(N);
  rep(i, M) {
    ll a, b;
    cin >> a >> b;
    a--;
    b--;
    G[a].push_back(b);
    G[b].push_back(a);
  }

  seen.assign(N, false);

  ll count = 0;
  rep(v, N) {
    if (seen[v])
      continue;
    dfs(G, v);
    count++;
  }

  cout << count - 1 << endl;
}


// Jay Shree Krishna

#include "bits/stdc++.h"
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)
#define ll long long int
#define inf 1e18 + 5
#define sr string
#define M 1000000007
#define start                                                                  \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define db(x) cout << #x << "=" << x << "\\n"
#define db2(a, b) cout << #a << ": " << a << " , " << #b << ": " << b << endl;
#define db3(a, b, c)                                                           \\
  cout << #a << ": " << a << " , " << #b << ": " << b << " , " << #c << ": "   \\
       << c << endl;
#define forr(i, n) for (int i = 0; i < n; i++)
#define fr(i, n) for (int i = 1; i <= n; i++)
#define rep(i, j, k) for (int i = j; i < k; i++)
#define rp(i, j, k) for (int i = j; i <= k; i++)
#define nn "\\n"
#define en "\\n"
#define vvi vector<vector<int>>
#define vi vector<int>
#define si set<int>
#define mlsi multiset<int>
#define vc vector<char>
#define vli vector<long long int>
#define vpii vector<pair<ll, ll>>
#define pii pair<ll, ll>
#define mii map<ll, ll>
#define msi map<string, int>
#define mci map<char, int>
#define umii unordered_map<int, int>
#define umsi unordered_map<string, int>
#define umci unordered_map<char, int>
#define mkp make_pair
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define sortv(x) sort(x.begin(), x.end())
#define sorta(x, n) sort(x, x + n)
#define sorted(x) is_sorted(x.begin(), x.end())
#define print_all(x)                                                           \\
  for (auto t : x)                                                             \\
    cout << t << " ";                                                          \\
  cout << en;
#define mxe(x) *max_element(all(x))
#define mne(x) *min_element(all(x))
#define ub upper_bound
#define lb lower_bound
#define go(x) for (auto &[u, v] : x)
#define in(x) cin >> x
#define in2(x, y) cin >> x >> y
#define in3(x, y, z) cin >> x >> y >> z
#define in4(x, y, z, v) cin >> x >> y >> z >> v
#define oo(x) cout << x << en
#define oo2(x, y) cout << x << " " << y << en
#define oo3(x, y, z) cout << x << " " << y << " " << z << en
#define ood(n, x) cout << fixed << setprecision(n) << x << "\\n"
#define sz(x) x.size()
#define ff first
#define ss second
#define ooy cout << "Yes\\n"
#define oon cout << "No\\n"
#define ooyy cout << "YES\\n"
#define oonn cout << "NO\\n"
#define rr return
#define cc continue

using namespace std;

template <class T> bool ckmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
template <class T> bool ckmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }

void read(vli &a, int n) {
  a.resize(n);
  forr(i, n) cin >> a[i];
}

bool fine = 0;
vli a, b;
ll n, m;
const int N = 1e5;
vi adj[N];
vi vis(N);

void solve() {
  in2(n, m);
  int u, v;
  vi edges;
  forr(i, m) {
    cin >> u >> v;
    u--;
    v--;
    adj[u].pb(v);
    adj[v].pb(u);
    edges.pb(u);
    edges.pb(v);
  }
  ll ans = 0;
  forr(i, sz(edges)) {
    int x = edges[i];
    if (!vis[i]) {
      queue<int> q;
      q.push(x);
      while (q.size()) {
        int curr = q.front();
        vis[curr] = 1;
        q.pop();
        for (auto v : adj[curr]) {
          if (!vis[v])
            q.push(v);
        }
      }
      ans++;
    }
  }
  oo(ans - 1);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("input1.txt", "r", stdin);
  freopen("error.txt", "w", stderr);
  freopen("output1.txt", "w", stdout);
#endif
  fast;
  if (fine) {
    start { solve(); }
  } else
    solve();
  return 0;
}

// Jay Shree Krishna

#include "bits/stdc++.h"
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)
#define ll long long int
#define inf 1e18 + 5
#define sr string
#define M 1000000007
#define start                                                                  \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define db(x) cout << #x << "=" << x << "\\n"
#define db2(a, b) cout << #a << ": " << a << " , " << #b << ": " << b << endl;
#define db3(a, b, c)                                                           \\
  cout << #a << ": " << a << " , " << #b << ": " << b << " , " << #c << ": "   \\
       << c << endl;
#define forr(i, n) for (int i = 0; i < n; i++)
#define fr(i, n) for (int i = 1; i <= n; i++)
#define rep(i, j, k) for (int i = j; i < k; i++)
#define rp(i, j, k) for (int i = j; i <= k; i++)
#define nn "\\n"
#define en "\\n"
#define vvi vector<vector<int>>
#define vi vector<int>
#define si set<int>
#define mlsi multiset<int>
#define vc vector<char>
#define vli vector<long long int>
#define vpii vector<pair<ll, ll>>
#define pii pair<ll, ll>
#define mii map<ll, ll>
#define msi map<string, int>
#define mci map<char, int>
#define umii unordered_map<int, int>
#define umsi unordered_map<string, int>
#define umci unordered_map<char, int>
#define mkp make_pair
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define sortv(x) sort(x.begin(), x.end())
#define sorta(x, n) sort(x, x + n)
#define sorted(x) is_sorted(x.begin(), x.end())
#define print_all(x)                                                           \\
  for (auto t : x)                                                             \\
    cout << t << " ";                                                          \\
  cout << en;
#define mxe(x) *max_element(all(x))
#define mne(x) *min_element(all(x))
#define ub upper_bound
#define lb lower_bound
#define go(x) for (auto &[u, v] : x)
#define in(x) cin >> x
#define in2(x, y) cin >> x >> y
#define in3(x, y, z) cin >> x >> y >> z
#define in4(x, y, z, v) cin >> x >> y >> z >> v
#define oo(x) cout << x << en
#define oo2(x, y) cout << x << " " << y << en
#define oo3(x, y, z) cout << x << " " << y << " " << z << en
#define ood(n, x) cout << fixed << setprecision(n) << x << "\\n"
#define sz(x) x.size()
#define ff first
#define ss second
#define ooy cout << "Yes\\n"
#define oon cout << "No\\n"
#define ooyy cout << "YES\\n"
#define oonn cout << "NO\\n"
#define rr return
#define cc continue

using namespace std;

template <class T> bool ckmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
template <class T> bool ckmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }

void read(vli &a, int n) {
  a.resize(n);
  forr(i, n) cin >> a[i];
}

bool fine = 0;
vli a, b;
ll n, m;
const int N = 1e5 + 9;
vi adj[N];
vi vis(N);

void solve() {
  in2(n, m);
  int u, v;
  vi edges;
  forr(i, m) {
    cin >> u >> v;
    u--;
    v--;
    adj[u].pb(v);
    adj[v].pb(u);
    edges.pb(u);
    edges.pb(v);
  }
  ll ans = 0;
  forr(i, n) {
    // int x=edges[i];
    if (!vis[i]) {
      queue<int> q;
      q.push(i);
      while (q.size()) {
        int curr = q.front();
        vis[curr] = 1;
        q.pop();
        for (auto v : adj[curr]) {
          if (!vis[v])
            q.push(v);
        }
      }
      ans++;
    }
  }
  oo(ans - 1);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("input1.txt", "r", stdin);
  freopen("error.txt", "w", stderr);
  freopen("output1.txt", "w", stdout);
#endif
  fast;
  if (fine) {
    start { solve(); }
  } else
    solve();
  return 0;
}


// Jay Shree Krishna

#include "bits/stdc++.h"
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)
#define ll long long int
#define inf 1e18 + 5
#define sr string
#define M 1000000007
#define start                                                                  \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define db(x) cout << #x << "=" << x << "\\n"
#define db2(a, b) cout << #a << ": " << a << " , " << #b << ": " << b << endl;
#define db3(a, b, c)                                                           \\
  cout << #a << ": " << a << " , " << #b << ": " << b << " , " << #c << ": "   \\
       << c << endl;
#define forr(i, n) for (int i = 0; i < n; i++)
#define fr(i, n) for (int i = 1; i <= n; i++)
#define rep(i, j, k) for (int i = j; i < k; i++)
#define rp(i, j, k) for (int i = j; i <= k; i++)
#define nn "\\n"
#define en "\\n"
#define vvi vector<vector<int>>
#define vi vector<int>
#define si set<int>
#define mlsi multiset<int>
#define vc vector<char>
#define vli vector<long long int>
#define vpii vector<pair<ll, ll>>
#define pii pair<ll, ll>
#define mii map<ll, ll>
#define msi map<string, int>
#define mci map<char, int>
#define umii unordered_map<int, int>
#define umsi unordered_map<string, int>
#define umci unordered_map<char, int>
#define mkp make_pair
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define sortv(x) sort(x.begin(), x.end())
#define sorta(x, n) sort(x, x + n)
#define sorted(x) is_sorted(x.begin(), x.end())
#define print_all(x)                                                           \\
  for (auto t : x)                                                             \\
    cout << t << " ";                                                          \\
  cout << en;
#define mxe(x) *max_element(all(x))
#define mne(x) *min_element(all(x))
#define ub upper_bound
#define lb lower_bound
#define go(x) for (auto &[u, v] : x)
#define in(x) cin >> x
#define in2(x, y) cin >> x >> y
#define in3(x, y, z) cin >> x >> y >> z
#define in4(x, y, z, v) cin >> x >> y >> z >> v
#define oo(x) cout << x << en
#define oo2(x, y) cout << x << " " << y << en
#define oo3(x, y, z) cout << x << " " << y << " " << z << en
#define ood(n, x) cout << fixed << setprecision(n) << x << "\\n"
#define sz(x) x.size()
#define ff first
#define ss second
#define ooy cout << "Yes\\n"
#define oon cout << "No\\n"
#define ooyy cout << "YES\\n"
#define oonn cout << "NO\\n"
#define rr return
#define cc continue

using namespace std;

template <class T> bool ckmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
template <class T> bool ckmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }

void read(vli &a, int n) {
  a.resize(n);
  forr(i, n) cin >> a[i];
}

bool fine = 0;
vli a, b;
ll n, m;
ll ans = 0;

const int N = 1e5 + 5;
vi ad[N];
vector<vector<int>> res;
vector<int> temp;
stack<int> st;

void dfs(vi adj[], vi &vis, int s) {
  vis[s] = 1;
  bool ok = true;
  for (auto x : adj[s]) {
    if (vis[x] == 0) {
      ok = false;
      dfs(adj, vis, x);
    }
  }
  st.push(s);
}

void dfs1(vi rev[], vi &vis, int s) {
  vis[s] = 1;
  temp.pb(s);
  for (auto x : rev[s]) {
    if (vis[x] == 0) {
      dfs1(rev, vis, x);
    }
  }
}
int kosaraju(int nd, vector<int> adj[]) {
  res.clear();
  temp.clear();
  vi vis(nd);
  vector<int> rev[6000];
  for (int i = 0; i < nd; i++) {
    if (vis[i] == 0) {
      dfs(adj, vis, i);
    }
  }
  vis.clear();
  vis.resize(nd);
  fill(vis.begin(), vis.end(), 0);

  for (int i = 0; i < nd; i++) {
    for (auto x : adj[i]) {
      rev[x].pb(i);
    }
  }
  while (!st.empty()) {
    int i = st.top();
    st.pop();
    if (!vis[i]) {
      dfs1(rev, vis, i);
      res.pb(temp);
      temp.clear();
    }
  }
  return res.size();
}

void solve() {
  in2(n, m);
  int u, v;
  int id;
  forr(i, m) {
    in2(u, v);
    u--;
    id = u;
    v--;
    ad[u].pb(v);
    ad[v].pb(u);
  }
  oo(kosaraju(n, ad) - 1);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("input1.txt", "r", stdin);
  freopen("error.txt", "w", stderr);
  freopen("output1.txt", "w", stdout);
#endif
  fast;
  if (fine) {
    start { solve(); }
  } else
    solve();
  return 0;
}

// Jay Shree Krishna

#include "bits/stdc++.h"
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL)
#define ll long long int
#define inf 1e18 + 5
#define sr string
#define M 1000000007
#define start                                                                  \\
  int t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
#define db(x) cout << #x << "=" << x << "\\n"
#define db2(a, b) cout << #a << ": " << a << " , " << #b << ": " << b << endl;
#define db3(a, b, c)                                                           \\
  cout << #a << ": " << a << " , " << #b << ": " << b << " , " << #c << ": "   \\
       << c << endl;
#define forr(i, n) for (int i = 0; i < n; i++)
#define fr(i, n) for (int i = 1; i <= n; i++)
#define rep(i, j, k) for (int i = j; i < k; i++)
#define rp(i, j, k) for (int i = j; i <= k; i++)
#define nn "\\n"
#define en "\\n"
#define vvi vector<vector<int>>
#define vi vector<int>
#define si set<int>
#define mlsi multiset<int>
#define vc vector<char>
#define vli vector<long long int>
#define vpii vector<pair<ll, ll>>
#define pii pair<ll, ll>
#define mii map<ll, ll>
#define msi map<string, int>
#define mci map<char, int>
#define umii unordered_map<int, int>
#define umsi unordered_map<string, int>
#define umci unordered_map<char, int>
#define mkp make_pair
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define sortv(x) sort(x.begin(), x.end())
#define sorta(x, n) sort(x, x + n)
#define sorted(x) is_sorted(x.begin(), x.end())
#define print_all(x)                                                           \\
  for (auto t : x)                                                             \\
    cout << t << " ";                                                          \\
  cout << en;
#define mxe(x) *max_element(all(x))
#define mne(x) *min_element(all(x))
#define ub upper_bound
#define lb lower_bound
#define go(x) for (auto &[u, v] : x)
#define in(x) cin >> x
#define in2(x, y) cin >> x >> y
#define in3(x, y, z) cin >> x >> y >> z
#define in4(x, y, z, v) cin >> x >> y >> z >> v
#define oo(x) cout << x << en
#define oo2(x, y) cout << x << " " << y << en
#define oo3(x, y, z) cout << x << " " << y << " " << z << en
#define ood(n, x) cout << fixed << setprecision(n) << x << "\\n"
#define sz(x) x.size()
#define ff first
#define ss second
#define ooy cout << "Yes\\n"
#define oon cout << "No\\n"
#define ooyy cout << "YES\\n"
#define oonn cout << "NO\\n"
#define rr return
#define cc continue

using namespace std;

template <class T> bool ckmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
template <class T> bool ckmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }

void read(vli &a, int n) {
  a.resize(n);
  forr(i, n) cin >> a[i];
}

bool fine = 0;
vli a, b;
ll n, m;
ll ans = 0;

const int N = 1e5 + 5;
vi ad[N];
vector<vector<int>> res;
vector<int> temp;
stack<int> st;

void dfs(vi adj[], vi &vis, int s) {
  vis[s] = 1;
  bool ok = true;
  for (auto x : adj[s]) {
    if (vis[x] == 0) {
      ok = false;
      dfs(adj, vis, x);
    }
  }
  st.push(s);
}

void dfs1(vi rev[], vi &vis, int s) {
  vis[s] = 1;
  temp.pb(s);
  for (auto x : rev[s]) {
    if (vis[x] == 0) {
      dfs1(rev, vis, x);
    }
  }
}
int kosaraju(int nd, vector<int> adj[]) {
  res.clear();
  temp.clear();
  vi vis(nd);
  vector<int> rev[N];
  for (int i = 0; i < nd; i++) {
    if (vis[i] == 0) {
      dfs(adj, vis, i);
    }
  }
  vis.clear();
  vis.resize(nd);
  fill(vis.begin(), vis.end(), 0);

  for (int i = 0; i < nd; i++) {
    for (auto x : adj[i]) {
      rev[x].pb(i);
    }
  }
  while (!st.empty()) {
    int i = st.top();
    st.pop();
    if (!vis[i]) {
      dfs1(rev, vis, i);
      res.pb(temp);
      temp.clear();
    }
  }
  return res.size();
}

void solve() {
  in2(n, m);
  int u, v;
  int id;
  forr(i, m) {
    in2(u, v);
    u--;
    v--;
    ad[u].pb(v);
    ad[v].pb(u);
  }
  oo(kosaraju(n, ad) - 1);
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("input1.txt", "r", stdin);
  freopen("error.txt", "w", stderr);
  freopen("output1.txt", "w", stdout);
#endif
  fast;
  if (fine) {
    start { solve(); }
  } else
    solve();
  return 0;
}


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

// Graph class represents a undirected graph
// using adjacency list representation
class Graph {
  // No. of vertices
  int V;

  // Pointer to an array containing adjacency lists
  list<int> *adj;

  // A function used by DFS
  void DFSUtil(int v, bool visited[]);

public:
  // Constructor
  Graph(int V);

  void addEdge(int v, int w);
  int NumberOfconnectedComponents();
};

// Function to return the number of
// connected components in an undirected graph
int Graph::NumberOfconnectedComponents() {

  // Mark all the vertices as not visited
  bool *visited = new bool[V];

  // To store the number of connected components
  int count = 0;
  for (int v = 0; v < V; v++)
    visited[v] = false;

  for (int v = 0; v < V; v++) {
    if (visited[v] == false) {
      DFSUtil(v, visited);
      count += 1;
    }
  }

  return count;
}

void Graph::DFSUtil(int v, bool visited[]) {

  // Mark the current node as visited
  visited[v] = true;

  // Recur for all the vertices
  // adjacent to this vertex
  list<int>::iterator i;

  for (i = adj[v].begin(); i != adj[v].end(); ++i)
    if (!visited[*i])
      DFSUtil(*i, visited);
}

Graph::Graph(int V) {
  this->V = V;
  adj = new list<int>[V];
}

// Add an undirected edge
void Graph::addEdge(int v, int w) {
  adj[v].push_back(w);
  adj[w].push_back(v);
}

// Driver code
int main() {
  int n, m;
  cin >> n >> m;

  Graph g(n);

  while (m--) {
    int a, b;
    cin >> a >> b;

    g.addEdge(a, b);
  }

  cout << g.NumberOfconnectedComponents() - 1;

  return 0;
}

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

// Graph class represents a undirected graph
// using adjacency list representation
class Graph {
  // No. of vertices
  int V;

  // Pointer to an array containing adjacency lists
  list<int> *adj;

  // A function used by DFS
  void DFSUtil(int v, bool visited[]);

public:
  // Constructor
  Graph(int V);

  void addEdge(int v, int w);
  int NumberOfconnectedComponents();
};

// Function to return the number of
// connected components in an undirected graph
int Graph::NumberOfconnectedComponents() {

  // Mark all the vertices as not visited
  bool *visited = new bool[V];

  // To store the number of connected components
  int count = 0;
  for (int v = 0; v < V; v++)
    visited[v] = false;

  for (int v = 0; v < V; v++) {
    if (visited[v] == false) {
      DFSUtil(v, visited);
      count += 1;
    }
  }

  return count;
}

void Graph::DFSUtil(int v, bool visited[]) {

  // Mark the current node as visited
  visited[v] = true;

  // Recur for all the vertices
  // adjacent to this vertex
  list<int>::iterator i;

  for (i = adj[v].begin(); i != adj[v].end(); ++i)
    if (!visited[*i])
      DFSUtil(*i, visited);
}

Graph::Graph(int V) {
  this->V = V;
  adj = new list<int>[V];
}

// Add an undirected edge
void Graph::addEdge(int v, int w) {
  adj[v].push_back(w);
  adj[w].push_back(v);
}

// Driver code
int main() {
  int n, m;
  cin >> n >> m;

  Graph g(n);

  while (m--) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    g.addEdge(a, b);
  }

  cout << g.NumberOfconnectedComponents() - 1;

  return 0;
}


#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mxn = 2e5 + 5;

class DisjointSet {
public:
  vector<int> parent;

  DisjointSet(int n) : parent(n) {
    for (int i = 0; i < n; i++) {
      parent[i] = i;
    }
  }

  void join(int a, int b) { parent[find(b)] = find(a); }

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

  bool check(int a, int b) { return find(a) == find(b); }
};

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int i, n, x, t1, t2;
  cin >> n >> x;
  DisjointSet dsu(n);
  for (i = 0; i < x; i++) {
    cin >> t1 >> t2;
    dsu.join(t1, t2);
  }
  int ans = 0;
  int t = dsu.find(0);
  for (i = 0; i < n; i++) {
    int temp = dsu.find(i);
    if (temp != t) {
      ans++;
      dsu.join(0, i);
    }
  }
  cout << ans;
  return 0;
}

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mxn = 2e5 + 5;

class DisjointSet {
public:
  vector<int> parent;

  DisjointSet(int n) : parent(n) {
    for (int i = 0; i < n; i++) {
      parent[i] = i;
    }
  }

  void join(int a, int b) { parent[find(b)] = find(a); }

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

  bool check(int a, int b) { return find(a) == find(b); }
};

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int i, n, x, t1, t2;
  cin >> n >> x;
  DisjointSet dsu(n);
  for (i = 0; i < x; i++) {
    cin >> t1 >> t2;
    t1--;
    t2--;
    dsu.join(t1, t2);
  }
  int ans = 0;
  int t = dsu.find(0);
  for (i = 0; i < n; i++) {
    int temp = dsu.find(i);
    if (temp != t) {
      ans++;
      dsu.join(0, i);
    }
  }
  cout << ans;
  return 0;
}


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

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() {
  long n, m, cnt = 0;
  cin >> n >> m;

  UnionFind tree(n);

  for (long i = 0; i < m; i++) {
    long a, b;
    cin >> a >> b;
    if (!tree.same(a, b)) {
      tree.unite(a, b);
      cnt++;
    }
  }

  cout << cnt;

  return 0;
}

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

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() {
  long n, m;
  cin >> n >> m;
  long a[m], b[m];
  UnionFind tree(n);

  for (long i = 0; i < m; i++) {
    cin >> a[i] >> b[i];
    tree.unite(a[i] - 1, b[i] - 1);
  }

  long cnt = 0;
  for (long i = 0; i < n; i++) {
    if (!tree.same(0, i)) {
      tree.unite(0, i);
      cnt++;
    }
  }

  cout << cnt;

  return 0;
}


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

using namespace std;

int dfs(list<int> graph[], int node, int visited[], set<int> &s) {
  if (visited[node] != -1)
    return 0;

  visited[node] = 1;
  s.erase(node);

  for (auto i : graph[node]) {
    dfs(graph, i, visited, s);
  }
}

int main() {
  int n, m;
  cin >> n >> m;
  list<int> graph[n];

  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    u--;
    v--;
    graph[u].push_back(v);
    graph[v].push_back(u);
  }

  int visited[n];

  for (int i = 0; i < n; i++)
    visited[i] = -1;

  set<int> s;
  for (int i = 0; i < n; i++)
    s.insert(i);

  int count = -1;
  // for(auto i:visited)
  // cout<<i<<" ";

  while (!s.empty()) {
    count++;
    auto ptrele = s.begin();
    auto ele = *ptrele;

    dfs(graph, ele, visited, s);
  }

  cout << count;

  return 0;
}

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

using namespace std;

int dfs(list<int> graph[], int node, int visited[], set<int> &s) {
  if (visited[node] != -1)
    return 0;

  visited[node] = 1;
  s.erase(node);

  for (auto i : graph[node]) {
    dfs(graph, i, visited, s);
  }

  return 1;
}

int main() {
  int n, m;
  cin >> n >> m;
  list<int> graph[n];

  for (int i = 0; i < m; i++) {
    int u, v;
    cin >> u >> v;
    u--;
    v--;
    graph[u].push_back(v);
    graph[v].push_back(u);
  }

  int visited[n];

  for (int i = 0; i < n; i++)
    visited[i] = -1;

  set<int> s;
  for (int i = 0; i < n; i++)
    s.insert(i);

  int count = -1;

  while (!s.empty()) {
    count++;
    auto ptrele = s.begin();
    auto ele = *ptrele;

    dfs(graph, ele, visited, s);
  }

  cout << count;

  return 0;
}


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

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() {
  int n, m;
  cin >> n >> m;
  vector<int> a(m), b(m);
  UnionFind UF(n);
  for (int i = 0; i < m; i++) {
    cin >> a[i] >> b[i];
    UF.unite(a[i], b[i]);
  }
  vector<int> root(n);
  for (int i = 0; i < n; i++) {
    root[UF.root(i)]++;
  }
  int ans = 0;
  for (int i = 0; i < n; i++) {
    if (root[i] != 0) {
      ans++;
    }
  }
  cout << ans - 1 << endl;
  return 0;
}

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

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() {
  int n, m;
  cin >> n >> m;
  vector<int> a(m), b(m);
  UnionFind UF(n);
  for (int i = 0; i < m; i++) {
    cin >> a[i] >> b[i];
    a[i]--;
    b[i]--;
    UF.unite(a[i], b[i]);
  }
  vector<int> root(n);
  for (int i = 0; i < n; i++) {
    root[UF.root(i)]++;
  }
  int ans = 0;
  for (int i = 0; i < n; i++) {
    if (root[i] != 0) {
      ans++;
    }
  }
  cout << ans - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
#include <map>
#include <set>
#include <unordered_map>
#include <vector>
#define lli long long int
#define loop(i, x, n) for (int i = x; i < n; i++)
#define loopr(i, x, n) for (int i = n - 1; i >= x; i--)
#define st set<lli>
#define v vector<lli>
#define um unordered_map<lli, lli>
#define vpl vector<pair<lli, lli>>
#define llu long long unsigned
#define ms muliset<int, greater<int>>
#define ums unordered_map<string, lli>
#define pb push_back
#define time                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);
#define test                                                                   \\
  lli t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
using namespace std;
int gcd(int a, int b) {
  if (b == 0)
    return a;
  return gcd(b, a % b);
}
v adj[100001];
lli vist[10001];

void dfs(lli node) {
  vist[node] = 1;
  for (lli child : adj[node])
    if (!vist[child])
      dfs(child);
}

int main() {
  time lli n, m;
  cin >> n >> m;
  loop(i, 0, m) {
    lli x, y;
    cin >> x >> y;
    adj[x].pb(y);
    adj[y].pb(x);
  }
  lli c = -1;
  loop(i, 1, n + 1) {
    if (!vist[i]) {
      dfs(i);
      c++;
    }
  }
  cout << c << endl;
  return 0;
}

#include <bits/stdc++.h>
#include <map>
#include <set>
#include <unordered_map>
#include <vector>
#define lli long long int
#define loop(i, x, n) for (int i = x; i < n; i++)
#define loopr(i, x, n) for (int i = n - 1; i >= x; i--)
#define st set<lli>
#define v vector<lli>
#define um unordered_map<lli, lli>
#define vpl vector<pair<lli, lli>>
#define llu long long unsigned
#define ms muliset<int, greater<int>>
#define ums unordered_map<string, lli>
#define pb push_back
#define time                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);
#define test                                                                   \\
  lli t;                                                                       \\
  cin >> t;                                                                    \\
  while (t--)
using namespace std;
int gcd(int a, int b) {
  if (b == 0)
    return a;
  return gcd(b, a % b);
}
v adj[100001];
lli vist[100001];

void dfs(lli node) {
  vist[node] = 1;
  for (lli child : adj[node])
    if (!vist[child])
      dfs(child);
}

int main() {
  time lli n, m;
  cin >> n >> m;
  loop(i, 0, m) {
    lli x, y;
    cin >> x >> y;
    adj[x].pb(y);
    adj[y].pb(x);
  }
  lli c = -1;
  loop(i, 1, n + 1) {
    if (!vist[i]) {
      dfs(i);
      c++;
    }
  }
  cout << c << endl;
  return 0;
}