//
//  main.cpp
//  Atcoder1
//
//  Created by Hamske on 2020/09/17.
//  Copyright © 2020 Author. All rights reserved.
//

#include <algorithm>
#include <iostream>
#include <queue>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

// #define pb push_back
// #define mp make_pair
#define fi first
#define se second

typedef long long ll;

// #define rep(_i,iv,n) for(int _i=(int)(iv);_i<(int)(n);++_i)
#define rep0(_i, n) for (int _i = 0; _i < (int)(n); ++_i)
// #define REP(_i,iv,_l,n) for(int _i=(int)(iv),_l=(int)(n);_i<_l;++_i)
#define REP0(_i, _l, n) for (int _i = 0, _l = (int)(n); _i < _l; ++_i)

// #define all(x) (x).begin(),(x).end()
#define REPV(v, _itr, _end)                                                    \\
  for (auto _itr = (v).begin(), _end = (v).end(); _itr != _end; ++_itr)
#define REPVC(v, _itr, _end)                                                   \\
  for (auto _itr = (v).cbegin(), _end = (v).cend(); _itr != _end; ++_itr)

#define td typedef
// #define tds(stl,type,name) typedef stl<type > name;
#define tdv(type, name) typedef vector<type> name;

typedef vector<bool> vb;
typedef vector<vb> vvb;
typedef vector<int> vi;
typedef vector<vi> vvi;
typedef vector<ll> vll;
typedef vector<vll> vvll;

#define INF 1 << 30

td pair<int, int> pii;
td priority_queue<pii, vector<pii>, greater<pii>> pq;

class UnionFind {
private:
  int *const graph;
  int *const rank;
  int grpNum;

public:
  UnionFind(const int N) : graph(new int[N]), rank(new int[N]), grpNum(N) {
    rep0(i, N) {
      graph[i] = i;
      rank[i] = 0;
    }
  }
  ~UnionFind() {
    delete[] graph;
    delete[] rank;
  }
  int find(int idx) {
    if (graph[idx] == idx) {
      return idx;
    } else {
      return graph[idx] = find(idx);
    }
  }
  bool merge(int idx1, int idx2) {
    int p1 = find(idx1);
    int p2 = find(idx2);
    if (p1 == p2) {
      return true;
    }
    if (rank[p1] < rank[p2]) {
      graph[p1] = p2;
    } else if (rank[p2] < rank[p1]) {
      graph[p2] = p1;
    } else {
      graph[p1] = p2;
      ++rank[p2];
    }
    --grpNum;
    return false;
  }
  int getGrpNum() const { return grpNum; }
};

int main(int argc, const char *argv[]) {
  int N, M;
  cin >> N >> M;

  UnionFind uf(N);
  rep0(i, M) {
    int a1, b1;
    cin >> a1 >> b1;
    --a1;
    --b1;
    uf.merge(a1, b1);
  }
  cout << (uf.getGrpNum() - 1) << endl;

  return 0;
}

//
//  main.cpp
//  Atcoder1
//
//  Created by Hamske on 2020/09/17.
//  Copyright © 2020 Author. All rights reserved.
//

#include <algorithm>
#include <iostream>
#include <queue>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

// #define pb push_back
// #define mp make_pair
#define fi first
#define se second

typedef long long ll;

// #define rep(_i,iv,n) for(int _i=(int)(iv);_i<(int)(n);++_i)
#define rep0(_i, n) for (int _i = 0; _i < (int)(n); ++_i)
// #define REP(_i,iv,_l,n) for(int _i=(int)(iv),_l=(int)(n);_i<_l;++_i)
#define REP0(_i, _l, n) for (int _i = 0, _l = (int)(n); _i < _l; ++_i)

// #define all(x) (x).begin(),(x).end()
#define REPV(v, _itr, _end)                                                    \\
  for (auto _itr = (v).begin(), _end = (v).end(); _itr != _end; ++_itr)
#define REPVC(v, _itr, _end)                                                   \\
  for (auto _itr = (v).cbegin(), _end = (v).cend(); _itr != _end; ++_itr)

#define td typedef
// #define tds(stl,type,name) typedef stl<type > name;
#define tdv(type, name) typedef vector<type> name;

typedef vector<bool> vb;
typedef vector<vb> vvb;
typedef vector<int> vi;
typedef vector<vi> vvi;
typedef vector<ll> vll;
typedef vector<vll> vvll;

#define INF 1 << 30

td pair<int, int> pii;
td priority_queue<pii, vector<pii>, greater<pii>> pq;

int find(vi &G, int i) {
  int pi = i;
  while (pi != G[pi]) {
    pi = G[pi];
  }
  while (G[i] != pi) {
    int temp = G[i];
    G[i] = pi;
    i = temp;
  }
  return pi;
}
bool sameRoot(vi &G, int i, const int j, int *const pi, int *const pj) {
  if (i == j) {
    return true;
  }
  int pi1 = i;
  bool sameFlg = false;
  while (pi1 != G[pi1]) {
    pi1 = G[pi1];
    if (pi1 == j) {
      sameFlg = true;
      break;
    }
  }
  while (G[i] != pi1) {
    int temp = G[i];
    G[i] = pi1;
    i = temp;
  }
  if (sameFlg) {
    return true;
  }
  *pi = pi1;
  *pj = find(G, j);
  return pi1 == *pj;
}

void mergeUF(vi &G, vi &rank, const int p1, const int p2) {
  if (rank[p1] > rank[p2]) {
    G[p2] = p1;
  } else if (rank[p1] > rank[p2]) {
    G[p1] = p2;
  } else {
    G[p2] = p1;
    ++rank[p1];
  }
}

int main(int argc, const char *argv[]) {
  int N, M;
  cin >> N >> M;

  vi G(N), rank(N, 0);
  rep0(i, N) { G[i] = i; }
  int ufNum = N;

  rep0(i, M) {
    int a1, b1;
    cin >> a1 >> b1;
    --a1;
    --b1;
    int pa, pb;
    if (!sameRoot(G, a1, b1, &pa, &pb)) {
      mergeUF(G, rank, pa, pb);
      --ufNum;
    }
  }
  cout << (ufNum - 1) << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
void dfs(int u, vector<vector<int>> &graph, int visited[], int count) {
  visited[u] = count;
  for (auto v : graph[u]) {
    if (!visited[v])
      dfs(v, graph, visited, count);
  }
}
int main() {

  int n, m;
  cin >> n >> m;
  int visited[n + 1];
  vector<vector<int>> graph(n + 1);
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    graph[u].push_back(v);
    graph[v].push_back(u);
  }
  int count = 0;
  for (int i = 1; i <= n; i++) {
    if (!visited[i]) {
      dfs(i, graph, visited, count);
      count++;
    }
  }
  cout << count << '\\n';
}

#include <bits/stdc++.h>
using namespace std;
void dfs(int u, vector<vector<int>> &graph, vector<bool> &visited) {
  visited[u] = true;
  for (auto v : graph[u]) {
    if (!visited[v])
      dfs(v, graph, visited);
  }
}
int main() {

  int n, m;
  cin >> n >> m;
  vector<bool> visited(n + 1, false);
  vector<vector<int>> graph(n + 1);
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    graph[u].push_back(v);
    graph[v].push_back(u);
  }
  int count = 0;
  for (int i = 1; i <= n; i++) {
    if (!visited[i]) {
      count++;
      dfs(i, graph, visited);
    }
  }
  cout << count - 1 << '\\n';
}


#include <bits/stdc++.h>
#define ll long long
#define all(a) a.begin(), a.end()
#define rall(a) a.rbegin(), a.rend()
#define sz(a) a.size()
#define re return
#define MP make_pair
#define pb push_back
#define pii pair<int, int>
#define se second
#define fi first
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define repd(i, a, b) for (int i = a; i >= b; i--)
using namespace std;
queue<int> q;
vector<int> edges[100005];
bool visited[100005];
int n, m;
int ans = 0;
void bfs(int x) {
  int ret = 0;
  visited[x] = 1;
  q.push(x);
  while (!q.empty()) {
    int nx = q.front();
    q.pop();
    rep(i, 0, sz(edges[nx]) - 1) {
      int y = edges[nx][i];
      if (!visited[y]) {
        visited[y] = 1;
        q.push(y);
      }
    }
  }
}
signed main() {
  ios_base::sync_with_stdio(0);
  // freopen("input.txt","r",stdin);
  // freopen("output.txt","w",stdout);
  cin >> n >> m;
  while (m--) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    edges[a].pb(b), edges[b].pb(a);
  }
  for (int i = 0; i < n; i++)
    if (!visited[i]) {
      ans++;
      bfs(i);
    }
  cout << ans - 1;
  re 0;
}

#include <bits/stdc++.h>
using namespace std;
queue<int> q;
vector<int> edges[100005];
bool visited[100005];
int n, m;
int ans = 0;
void bfs(int x) {
  int ret = 0;
  visited[x] = 1;
  q.push(x);
  while (!q.empty()) {
    int nx = q.front();
    q.pop();
    for (int i = 0; i < edges[nx].size(); i++) {
      int y = edges[nx][i];
      if (!visited[y]) {
        visited[y] = 1;
        q.push(y);
      }
    }
  }
}
int main() {
  cin >> n >> m;
  while (m--) {
    int a, b;
    cin >> a >> b;
    a--, b--;
    edges[a].push_back(b), edges[b].push_back(a);
  }
  for (int i = 0; i < n; i++)
    if (!visited[i]) {
      ans++;
      bfs(i);
    }
  cout << ans - 1;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ii = pair<int, int>;
using iii = pair<ii, int>;
#define vt vector
#define pq priority_queue
#define pb push_back
#define mp make_pair
#define fi first
#define se second
const int MOD = 1e9 + 7;
const int INF = 2e9;
const int N = 1e5 + 5;

int n, m, a, b, ans;
int par[N];
bool vis[N];

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

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

void join(int x, int y) { par[root(x)] = y; }

void solve() {
  int T = 1;
  // cin >> T;
  for (int tc = 1; tc <= T; tc++) {
    cin >> n >> m;
    init();
    while (m--) {
      cin >> a >> b;
      join(a, b);
    }
    for (int i = 1; i <= n; i++) {
      if (!vis[root(i)]) {
        vis[root(i)] = true;
        ans++;
      }
    }
    cout << ans - 1 << "\\n";
  }
}

int main() {

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

  solve();
}

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ii = pair<int, int>;
using iii = pair<ii, int>;
#define vt vector
#define pq priority_queue
#define pb push_back
#define mp make_pair
#define fi first
#define se second
const int MOD = 1e9 + 7;
const int INF = 2e9;
const int N = 1e5 + 5;

int n, m, a, b, ans;
int par[N];
bool vis[N];

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

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

void join(int x, int y) { par[root(x)] = root(y); }

void solve() {
  int T = 1;
  // cin >> T;
  for (int tc = 1; tc <= T; tc++) {
    cin >> n >> m;
    init();
    while (m--) {
      cin >> a >> b;
      join(a, b);
    }
    for (int i = 1; i <= n; i++) {
      if (!vis[root(i)]) {
        vis[root(i)] = true;
        ans++;
      }
    }
    cout << ans - 1 << "\\n";
  }
}

int main() {

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

  solve();
}


#include <iostream>
#include <vector>

using namespace std;

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

void C(int a, int b, vector<int> &p) { p[P(a, p)] = P(b, p); }

int main() {
  int N, M;
  cin >> N >> M;
  vector<int> p(N);
  int i;
  for (i = 0; i < N; i++)
    p[i] = i;
  int a, b;
  for (i = 0; i < M; i++) {
    cin >> a >> b;
    C(a, b, p);
  }
  vector<bool> f(N, false);
  int cnt = 0;
  for (i = 0; i < N; i++) {
    if (!f[P(i, p)]) {
      f[p[i]] = true;
      cnt++;
    }
  }
  cout << cnt - 1 << endl;
}

#include <iostream>
#include <vector>

using namespace std;

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

void C(int a, int b, vector<int> &p) { p[P(a, p)] = P(b, p); }

int main() {
  int N, M;
  cin >> N >> M;
  vector<int> p(N);
  int i;
  for (i = 0; i < N; i++)
    p[i] = i;
  int a, b;
  for (i = 0; i < M; i++) {
    cin >> a >> b;
    C(a - 1, b - 1, p);
  }
  vector<bool> f(N, false);
  int cnt = 0;
  for (i = 0; i < N; i++) {
    if (!f[P(i, p)]) {
      f[p[i]] = true;
      cnt++;
    }
  }
  cout << cnt - 1 << endl;
}


/*
       ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄
      ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌
      ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌
      ▐░▌          ▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌ ▄▄▄▄▄▄▄▄ ▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌▐░░░░░░░░▌▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌ ▀▀▀▀▀▀█░▌▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌       ▐░▌▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌
      ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌
       ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀

 #can_change_the_world_with_the_help_of_0_1
                                    -darshan786    */

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mod 1000000007
#define boost                                                                  \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(0);
#define F first
#define MAXN 100005
#define int long long
#define S second
#define all(v) v.begin(), v.end()
#define PI pair<int, int>
#define PIS pair<int, string>

int values[41];
int vis[10005];
void value() {
  values[0] = 1;
  for (int i = 1; i <= 40; i++) {
    values[i] = 2 * values[i - 1];
  }
}
vector<int> adj[MAXN];
void dfs(int u) {
  vis[u] = 1;
  for (auto x : adj[u]) {
    if (!vis[x]) {
      dfs(x);
    }
  }
}
int32_t main() {
  boost
#ifndef ONLINE_JUDGE
      freopen("input.c", "r", stdin);
  freopen("output.c", "w", stdout);
#endif

  int n, m;
  cin >> n >> m;
  value();
  for (int i = 0; i < m; ++i) {
    int u, v;
    cin >> u >> v;
    adj[u].push_back(v);
    adj[v].push_back(u);
  }
  int ans = 0;
  for (int i = 1; i <= n; ++i) {
    if (!vis[i]) {
      ans++;
      memset(vis, 0, sizeof vis);
      dfs(i);
    }
  }
  cout << ans - 1;
}

/*
       ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄
      ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌
      ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌
      ▐░▌          ▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌ ▄▄▄▄▄▄▄▄ ▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌▐░░░░░░░░▌▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌ ▀▀▀▀▀▀█░▌▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░▌       ▐░▌▐░▌       ▐░▌▐░▌       ▐░▌
      ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌
      ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░▌
       ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀

 #can_change_the_world_with_the_help_of_0_1
                                    -darshan786    */

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mod 1000000007
#define boost                                                                  \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(0);
#define F first
#define MAXN 100005
#define int long long
#define S second
#define all(v) v.begin(), v.end()
#define PI pair<int, int>
#define PIS pair<int, string>

int values[41];
int vis[100005] = {0};
void value() {
  values[0] = 1;
  for (int i = 1; i <= 40; i++) {
    values[i] = 2 * values[i - 1];
  }
}
vector<int> adj[MAXN];
void dfs(int u) {
  vis[u] = 1;
  for (auto x : adj[u]) {
    if (!vis[x]) {
      dfs(x);
    }
  }
}
int32_t main() {
  boost

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

      dfs(i);
    }
  }
  cout << ans - 1;
}


#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define printVec(v)                                                            \\
  printf("{");                                                                 \\
  for (const auto &i : v) {                                                    \\
    std::cout << i << ", ";                                                    \\
  }                                                                            \\
  printf("}\\n");
#define degreeToRadian(deg) (((deg) / 360) * 2 * M_PI)
#define radianTodegree(rad) (((rad) / 2 / M_PI) * 360)
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return 1;
  }
  return 0;
}
using namespace std;
using P = pair<int, int>;
using ll = long long;
const ll INF = 1LL << 60;
const int MOD = 1e9 + 7;
const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, 1, 0, -1};

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

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

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

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

// cin.tie(0);ios::sync_with_stdio(false);
// cout << fixed << setprecision(15) << y << endl;
int main() {
  int n, m;
  cin >> n >> m;
  UnionFind uf(n);
  rep(i, m) {
    int a, b;
    --a;
    --b;
    cin >> a >> b;
    uf.unite(a, b);
  }

  set<int> groups;
  rep(i, n) groups.insert(uf.root(i));
  cout << groups.size() - 1 << endl;
  return 0;
}

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define printVec(v)                                                            \\
  printf("{");                                                                 \\
  for (const auto &i : v) {                                                    \\
    std::cout << i << ", ";                                                    \\
  }                                                                            \\
  printf("}\\n");
#define degreeToRadian(deg) (((deg) / 360) * 2 * M_PI)
#define radianTodegree(rad) (((rad) / 2 / M_PI) * 360)
template <class T> inline bool chmax(T &a, T b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> inline bool chmin(T &a, T b) {
  if (a > b) {
    a = b;
    return 1;
  }
  return 0;
}
using namespace std;
using P = pair<int, int>;
using ll = long long;
const ll INF = 1LL << 60;
const int MOD = 1e9 + 7;
const int dx[4] = {1, 0, -1, 0};
const int dy[4] = {0, 1, 0, -1};

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

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

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

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

// cin.tie(0);ios::sync_with_stdio(false);
// cout << fixed << setprecision(15) << y << endl;
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);
  }

  set<int> groups;
  rep(i, n) groups.insert(uf.root(i));
  cout << groups.size() - 1 << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
int p[100010], ans, a[100010], b[100010], son[100010];
int n, m;
int fifa(int x) {
  int t = x;
  while (p[t] != t) {
    t = p[t];
  }
  while (p[x] != t) {
    int tmp = p[x];
    p[x] = t;
    x = tmp;
  }
}
bool join(int x, int y) {
  int px = fifa(x);
  int py = fifa(y);
  if (px != py) {
    p[px] = py;
    son[py] += son[px];
    if (son[py] == n) {
      return true;
    }
    return false;
  }
  return false;
}
bool query(int x, int y) { return fifa(x) == fifa(y); }
int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) {
    p[i] = i;
    son[i] = 1;
  }
  for (int i = 1; i <= m; i++) {
    scanf("%d%d", &a[i], &b[i]);
  }
  for (int i = 1; i <= m; i++) {
    int x = fifa(a[i]), y = fifa(b[i]);
    if (!join(x, y)) {
      ans++;
    }
  }
  printf("%d", ans);
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
int p[100010], ans, a, b;
int n, m;
int fifa(int x) {
  if (p[x] == x)
    return x;
  int root = fifa(p[x]);
  return p[x] = root;
}
void join(int x, int y) {
  int px = fifa(x);
  int py = fifa(y);
  if (px != py) {
    p[px] = py;
  }
}
int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) {
    p[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    int x, y;
    scanf("%d%d", &x, &y);
    join(x, y);
  }
  for (int i = 1; i <= n; i++) {
    if (fifa(i) == i) {
      ans++;
    }
  }
  printf("%d", ans - 1);
  return 0;
}


#include <bits/stdc++.h>
#define rep(begin, end) for (i = begin; i < end; i++)
#define repj(begin, end) for (j = begin; j < end; j++)
#define init(arr, end, val)                                                    \\
  for (i = 0; i < end; i++)                                                    \\
    arr[i] = val;
#define printint(i0, i1) printf("%d %d\\n", i0, i1)

using namespace std;
typedef long long int ll;
const int inf = 1000000000;
const int mod = 1000000007;
const int nil = -1;
int i, n, m, k, a, b;
vector<int> v[100000];
bool checked[100000];
queue<int> q;

void bfs(int num) {}

int main() {
  scanf(" %d %d", &m, &n);
  rep(0, n) {
    scanf(" %d %d", &a, &b);
    v[a].push_back(b);
    v[b].push_back(a);
    checked[i] = false;
  }
  int cnt = 0;
  rep(0, n) {
    if (!checked[i]) {
      cnt++;
      q.push(i);
      while (q.size() != 0) {
        int cur = q.front();
        q.pop();
        checked[cur] = true;
        for (auto it = v[cur].begin(); it != v[cur].end(); it++) {
          if (!checked[*it])
            q.push(*it);
        }
      }
    }
  }
  printf("%d\\n", cnt);
}

#include <bits/stdc++.h>
#define rep(begin, end) for (i = begin; i < end; i++)
#define repj(begin, end) for (j = begin; j < end; j++)
#define init(arr, end, val)                                                    \\
  for (i = 0; i < end; i++)                                                    \\
    arr[i] = val;
#define printint(i0, i1) printf("%d %d\\n", i0, i1)

using namespace std;
typedef long long int ll;
const int inf = 1000000000;
const int mod = 1000000007;
const int nil = -1;
int i, n, m, k, a, b;
bool checked[100001];
queue<int> q;
set<pair<int, int>> s;

int main() {
  scanf(" %d %d", &n, &m);
  rep(1, n + 1) checked[i] = false;
  rep(0, m) {
    scanf(" %d %d", &a, &b);
    s.insert(make_pair(a, b));
    s.insert(make_pair(b, a));
  }
  int cnt = 0;
  rep(1, n + 1) {
    if (!checked[i]) {
      cnt++;
      q.push(i);
      while (q.size() != 0) {
        int cur = q.front();
        q.pop();
        checked[cur] = true;
        for (auto it = s.lower_bound(make_pair(cur, -1));
             it != s.lower_bound(make_pair(cur + 1, -1)); it++) {
          if (!checked[it->second])
            q.push(it->second);
        }
      }
    }
  }
  printf("%d\\n", cnt - 1);
}


#include <iostream>
#include <stdio.h>
#include <vector>
// #include <bits/stdc++.h>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <queue>
#include <string>

using namespace std;
typedef long long ll;

#define rep(i, k, n) for (ll i = k; i < (ll)(n); i++)

struct UnionFind {
  vector<ll> par; // par[a]=b: aの親がb

  UnionFind(ll N) : par(N) { // 初めは全て根として初期化
    rep(i, 0, N) par[i] = i;
  }

  ll root(ll x) { // xの属する根を求める
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(ll x, ll y) { // xとyの木を併合
    ll rx = root(x);
    ll ry = root(y);
    if (rx == ry)
      return; // 根が同じときはそのまま
    par[rx] = ry;
  }

  bool same(ll x, ll y) { // x,y の属する木が同じならtrue
    return root(x) == root(y);
  }
};

int main() {
  ll n, m;
  cin >> n >> m;
  UnionFind tree(n);
  vector<ll> a(m), b(m);
  rep(i, 0, m) {
    cin >> a[i] >> b[i];
    tree.unite(a[i] - 1, b[i] - 1);
  }

  ll ans = 0;
  rep(i, 0, n - 1) {
    rep(j, i + 1, n) {
      if (tree.same(i, j)) {
        // cout << "Yes" << endl;
      } else {
        // cout << "No" << endl;
        tree.unite(i, j);
        ans++;
      }
    }
  }

  cout << ans << endl;

  return 0;
}

#include <iostream>
#include <stdio.h>
#include <vector>
// #include <bits/stdc++.h>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <queue>
#include <string>

using namespace std;
typedef long long ll;

#define rep(i, k, n) for (ll i = k; i < (ll)(n); i++)

struct UnionFind {
  vector<ll> par; // par[a]=b: aの親がb

  UnionFind(ll N) : par(N) { // 初めは全て根として初期化
    rep(i, 0, N) par[i] = i;
  }

  ll root(ll x) { // xの属する根を求める
    if (par[x] == x)
      return x;
    return par[x] = root(par[x]);
  }

  void unite(ll x, ll y) { // xとyの木を併合
    ll rx = root(x);
    ll ry = root(y);
    if (rx == ry)
      return; // 根が同じときはそのまま
    par[rx] = ry;
  }

  bool same(ll x, ll y) { // x,y の属する木が同じならtrue
    return root(x) == root(y);
  }
};

int main() {
  ll n, m;
  cin >> n >> m;
  UnionFind tree(n);
  vector<ll> a(m), b(m);
  rep(i, 0, m) {
    cin >> a[i] >> b[i];
    --a[i];
    --b[i];
    tree.unite(a[i], b[i]);
  }

  ll ans = 0;
  vector<ll> group_size(n);
  rep(i, 0, n) { // 各グループの人数を数える
    group_size[tree.root(i)]++;
  }
  rep(i, 0, n) {
    if (group_size[i] > 0)
      ans++;
  }

  cout << ans - 1 << endl;

  return 0;
}


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

#define ll long long
const int N = 2e5 + 9;

vector<int> adj[N];
int n, m;
bool vis[N];
void dfs(int u, int par) {
  vis[u] = 1;
  for (auto v : adj[u]) {
    if (v == par)
      continue;
    dfs(v, u);
  }
}

int main() {

  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    adj[u].push_back(v);
    adj[v].push_back(u);
  }
  ll ans = 0;
  for (int i = 1; i <= n; i++) {
    if (vis[i] == 0) {
      dfs(i, 0);
      ans++;
    }
  }
  cout << max(0LL, ans - 1) << endl;
}

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

#define ll long long
const int N = 2e5 + 9;

vector<int> adj[N];
int n, m;
bool vis[N];
void dfs(int u) {
  vis[u] = 1;
  for (auto v : adj[u]) {
    if (vis[v] == 1)
      continue;
    dfs(v);
  }
}

int main() {

  cin >> n >> m;
  for (int i = 1; i <= m; i++) {
    int u, v;
    cin >> u >> v;
    adj[u].push_back(v);
    adj[v].push_back(u);
  }
  ll ans = 0;
  for (int i = 1; i <= n; i++) {
    if (vis[i] == 0) {
      dfs(i);
      ans++;
    }
  }
  cout << max(0LL, ans - 1) << endl;
}


// In the name of god
#include <bits/stdc++.h>
#define F first
#define S second
#define pb push_back
#define all(x) x.begin(), x.end()
#define use_file                                                               \\
  freopen("input.txt", "r", stdin);                                            \\
  freopen("output.txt", "w", stdout);
using namespace std;
using ll = long long;
const ll N = 1e5, inf = 4e18, mod = 1e9 + 7;
ll n, m, ans;
vector<ll> adj[N];
bool mark[N];
void dfs(ll u) {
  mark[u] = 1;
  for (int j : adj[u]) {
    if (!mark[j])
      dfs(j);
  }
}
int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    ll u, v;
    cin >> u >> v;
    adj[u].pb(v);
    adj[v].pb(u);
  }
  for (int i = 1; i <= n; i++) {
    if (!mark[i]) {
      dfs(i);
      ans++;
    }
  }
  cout << ans - 1;
  cout << endl;
}

// In the name of god
#include <bits/stdc++.h>
#define F first
#define S second
#define pb push_back
#define all(x) x.begin(), x.end()
#define use_file                                                               \\
  freopen("input.txt", "r", stdin);                                            \\
  freopen("output.txt", "w", stdout);
using namespace std;
using ll = long long;
const ll N = 1e5 + 5, inf = 4e18, mod = 1e9 + 7;
ll n, m, ans;
vector<ll> adj[N];
bool mark[N];
void dfs(ll u) {
  mark[u] = 1;
  for (int j : adj[u]) {
    if (!mark[j])
      dfs(j);
  }
}
int main() {
  ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    ll u, v;
    cin >> u >> v;
    adj[u].pb(v);
    adj[v].pb(u);
  }
  for (int i = 1; i <= n; i++) {
    if (!mark[i]) {
      dfs(i);
      ans++;
    }
  }
  cout << ans - 1;
  cout << endl;
}


#pragma GCC optimize(2)
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
#define read(a) scanf("%d", &a)
#define readf(a) scanf("%lf", &a)
#define pb push_back
#define mk make_pair
#define _rand mt19937 RAND(time(0))
#define mem(a) memset(a, 0, sizeof(a))
#define Buff ios::sync_with_stdio(false)
typedef long long ll;
using namespace std;
const int INF = 1e9 + 7;
const int N = 3e5 + 7;
const int M = 1e6 + 7;

#define lson rt << 1
#define rson rt << 1 | 1
template <class T> class SegTree {
public:
  T maxv[N << 2];
  void init() { mem(maxv); }
  inline void pushup(int rt) { maxv[rt] = max(maxv[lson], maxv[rson]); }
  void update(int rt, int l, int r, int pos, T val) {
    if (l == r) {
      maxv[rt] = max(maxv[rt], val);
      return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
      update(lson, l, mid, pos, val);
    else
      update(rson, mid + 1, r, pos, val);
    pushup(rt);
  }

  T query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R)
      return maxv[rt];
    int mid = (l + r) >> 1;
    T ans = 0;
    if (L <= mid)
      ans = max(ans, query(lson, l, mid, L, R));
    if (R > mid)
      ans = max(ans, query(rson, mid + 1, r, L, R));
    return ans;
  }
};
SegTree<int> tree;

int fa[N];

int get(int x) { return fa[x] == x ? x : get(fa[x]); }
signed main() {
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++)
    fa[i] = i;
  while (m--) {
    int u, v;
    cin >> u >> v;
    u = get(u);
    v = get(v);
    fa[u] = v;
  }

  int cnt = 0;
  for (int i = 1; i <= n; i++)
    if (i == get(i))
      cnt++;

  cout << cnt - 1 << "\\n";
  return 0;
}
/**
 **/

#pragma GCC optimize(2)
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <vector>
#define read(a) scanf("%d", &a)
#define readf(a) scanf("%lf", &a)
#define pb push_back
#define mk make_pair
#define _rand mt19937 RAND(time(0))
#define mem(a) memset(a, 0, sizeof(a))
#define Buff ios::sync_with_stdio(false)
typedef long long ll;
using namespace std;
const int INF = 1e9 + 7;
const int N = 3e5 + 7;
const int M = 1e6 + 7;
int fa[N];

int get(int x) { return fa[x] = fa[x] == x ? x : get(fa[x]); }
signed main() {
  Buff;
  int n, m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++)
    fa[i] = i;
  while (m--) {
    int u, v;
    cin >> u >> v;
    u = get(u);
    v = get(v);
    fa[u] = v;
  }

  int cnt = 0;
  for (int i = 1; i <= n; i++)
    if (i == get(i))
      cnt++;

  cout << cnt - 1 << "\\n";
  return 0;
}
/**
 **/


#include <algorithm>
#include <cmath>
#include <iostream>
#include <set>
#include <utility>
#include <vector>

class UnionFind {
  std::vector<int> r;

public:
  UnionFind(int N) {
    for (int i = -1; i > -N - 1; i--) {
      r.push_back(i);
    }
  }
  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]) {
      std::swap(x, y);
    }
    r[x] += r[y];
    r[y] = x;
    return true;
  }
  int show() {
    std::set<int> jud;
    int co = 0;
    for (int i : r) {
      auto iter = jud.find(i);
      if (iter == jud.end()) {
        co += 1;
        jud.insert(i);
      }
    }
    return co;
  }
};

int main() {
  int n, m;
  std::cin >> n >> m;
  UnionFind UF(n);
  for (int i = 0; i < m; i++) {
    int a, b;
    std::cin >> a >> b;
    a--;
    b--;
    UF.unite(a, b);
  }
  std::cout << UF.show() - 1 << std::endl;
}

#include <algorithm>
#include <cmath>
#include <iostream>
#include <set>
#include <utility>
#include <vector>

class UnionFind {
  std::vector<int> r, rank;

public:
  UnionFind(int N) {
    for (int i = 0; i < N; i++) {
      r.push_back(i);
      rank.push_back(0);
    }
  }
  int root(int x) {
    if (r[x] == x) {
      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 (rank[x] < rank[y]) {
      r[x] = y;
    } else {
      r[y] = x;
      if (rank[x] == rank[y]) {
        rank[x] += 1;
      }
    }
    return true;
  }
  int show() {
    std::set<int> jud;
    int co = 0;
    for (int i : r) {
      auto iter = jud.find(root(i));
      if (iter == jud.end()) {
        co += 1;
        jud.insert(root(i));
      }
    }
    return co;
  }
};

int main() {
  int n, m;
  std::cin >> n >> m;
  UnionFind UF(n);
  for (int i = 0; i < m; i++) {
    int a, b;
    std::cin >> a >> b;
    a--;
    b--;
    UF.unite(a, b);
  }
  std::cout << UF.show() - 1 << std::endl;
}


#include <bits/stdc++.h>
using namespace std;
long long int p[10001];
int main() {
  int m, n, a, b, k = 0, ma = 0;
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= m; i++) {
    scanf("%d%d", &a, &b);
    p[a]++;
    p[b]++;
  }
  for (int i = 1; i <= n; i++)
    if (p[i] > ma)
      ma = p[i];
  k = n - ma - 1;
  printf("%d", k);
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
const int N = 100001;
vector<int> G[N];
int vis[N];
void dfs(int u) {
  vis[u] = 1;
  for (int i = 0; i < G[u].size(); i++) {
    int v = G[u][i];
    if (vis[v] == 0)
      dfs(v);
  }
}
int main() {
  int n, m;
  cin >> n >> m;
  while (m--) {
    int u, v;
    cin >> u >> v;
    G[u].push_back(v);
    G[v].push_back(u);
  }
  int cnt = 0;
  for (int i = 1; i <= n; i++) {
    if (vis[i] == 0) {
      dfs(i);
      cnt++;
    }
  }
  cout << cnt - 1 << endl;
  return 0;
}


#include <cstdio>
#include <iostream>
#include <map>
using namespace std;

map<int, int> ds; // 上下级关系
map<int, int> ranks;

int FindSup(int root) {
  if (ds[root] == root)
    return root;
  ds[root] = FindSup(ds[root]);
}

void Union(int root1, int root2) {
  int x = FindSup(root1);
  int y = FindSup(root2);
  if (x == y)
    return;
  if (ranks[x] > ranks[y])
    ds[y] = x;
  else if (ranks[x] < ranks[y])
    ds[x] = y;
  else {
    ds[x] = y;
    ranks[y]++;
  }
}

int main() {
  int N, M, cnt = 0;
  int x, y;
  scanf("%d %d", &N, &M);

  for (int i = 1; i <= N; i++) {
    ds[i] = i;
    ranks[i] = 0;
  }

  for (int i = 0; i < M; i++) {
    scanf("%d %d", &x, &y);
    if (FindSup(x) != FindSup(y)) {
      Union(x, y);
      cnt++;
    }
  }
  cnt = N - 1 - cnt;
  if (cnt < 0)
    cnt = 0;
  printf("%d", cnt);

  return 0;
}

#include <cstdio>
#include <iostream>
#include <map>
using namespace std;

map<int, int> ds; // 上下级关系
// map<int, int> ranks;

int FindSup(int root) {
  if (ds[root] == root)
    return root;
  ds[root] = FindSup(ds[root]);
  return ds[root];
}

void Union(int root1, int root2) {
  int x = FindSup(root1);
  int y = FindSup(root2);
  if (x == y)
    return;
  if (x > y)
    ds[y] = x;
  else
    ds[x] = y;
  /*else
  {
          ds[x] = y;
          ranks[y]++;
  }*/
}

int main() {
  int N, M, cnt = 0;
  int x, y;
  scanf("%d %d", &N, &M);

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

  for (int i = 0; i < M; i++) {
    scanf("%d %d", &x, &y);
    if (FindSup(x) != FindSup(y)) {
      Union(x, y);
      cnt++;
    }
  }
  cnt = N - 1 - cnt;
  if (cnt < 0)
    cnt = 0;
  printf("%d", cnt);

  return 0;
}


#include <cstdio>
#include <iostream>
#include <map>
using namespace std;

map<int, int> ds; // 上下级关系

int FindSup(int root) {
  if (ds[root] == root)
    return root;
  ds[root] = FindSup(ds[root]);
}

void Union(int root1, int root2) {
  int x = FindSup(root1);
  int y = FindSup(root2);
  if (x == y)
    return;
  if (x > y)
    ds[y] = x;
  else
    ds[x] = y;
}

int main() {
  int N, M, cnt = 0;
  int x, y;
  scanf("%d %d", &N, &M);

  for (int i = 1; i <= N; i++)
    ds[i] = i;

  for (int i = 0; i < M; i++) {
    scanf("%d %d", &x, &y);
    if (FindSup(x) != FindSup(y)) {
      Union(x, y);
      cnt++;
    }
  }
  cnt = N - 1 - cnt;
  if (cnt < 0)
    cnt = 0;
  printf("%d", cnt);

  return 0;
}

#include <bits/stdc++.h>

using namespace std;

map<int, int> ds;
map<int, int> ranks;

int find_root(int x) { return x == ds[x] ? x : ds[x] = find_root(ds[x]); }

void union_set(int x, int y) {
  int x_root = find_root(x);
  int y_root = find_root(y);
  if (x_root != y_root) {
    if (ranks[x_root] > ranks[y_root]) {
      ds[y_root] = x_root;
    } else if (ranks[x_root] < ranks[y_root]) {
      ds[x_root] = y_root;
    } else {
      ds[x_root] = y_root;
      ranks[y_root]++;
    }
  }
}

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

  // 初始化
  for (int i = 1; i <= n; i++) {
    ds[i] = i;
    ranks[i] = 0;
  }

  int cnt = 0;
  for (int i = 1; i <= m; i++) {
    int x, y;
    cin >> x >> y;

    x = find_root(x);
    y = find_root(y);
    if (x == y) {
      continue;
    }

    union_set(x, y);
    cnt++;
  }

  cnt = n - 1 - cnt;
  if (cnt < 0) {
    cnt = 0;
  }
  cout << cnt << "\\n";

  return 0;
}


// 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() {

  // 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);
  int a, b;
  while (m--) {
    cin >> a >> b;
    g.addEdge(a, b);
  }

  cout << g.NumberOfconnectedComponents() - 1;

  return 0;
}

/*#include <ext/pb_ds/assoc_container.hpp>
using namespace __gnu_pbds;*/
#include <algorithm>
#include <bits/stdc++.h>
#include <ctype.h>
#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define S second
#define F first
#define vi vector<ll>
#define pii pair<ll, ll>
#define bc __builtin_popcountll
#define vvi vector<vi>
#define vpii vector<pair<ll, ll>>
#define mll unordered_map<ll, ll>
#define INF LONG_LONG_MAX
#define NINF LONG_LONG_MIN
#define fo(i, a, b) for (i = a; i <= b; i++)
#define f(i, n) for (ll i = 0; i < n; i++)
#define f1(i, n) for (ll i = 1; i <= n; i++)
#define fb(i, n) for (ll i = n - 1; i >= 0; i--)
#define fb1(i, n) for (ll i = n; i > 0; i--)
#define endd cout << '\\n';
#define deb(x) cout << #x << "=" << x << endl
#define deb2(x, y) cout << #x << "=" << x << "," << #y << "=" << y << endl
#define fr(it, a) for (auto it = a.begin(); it != a.end(); it++)
#define PI 3.1415926535897932384626
#define clr(x) memset(x, 0, sizeof(x))
#define sortall(x) sort(all(x))
#define all(x) x.begin(), x.end()
#define fast                                                                   \\
  ios_base::sync_with_stdio(0);                                                \\
  cin.tie(0);                                                                  \\
  cout.tie(0)
#define CASES                                                                  \\
  ll t;                                                                        \\
  cin >> t;                                                                    \\
  while (t--)
typedef long long ll;
typedef unsigned long long ull;
// typedef
// tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>
// indexed_set; typedef
// tree<ll,null_type,less<ll>,rb_tree_tag,tree_order_statistics_node_update>ordered_set;
//*X.find_by_order()
// X.order_of_key()
struct cmp {
  bool operator()(ll a, ll b) { return (a > b); }
};
ll ceil(const ll &a, const ll &b) {
  if (a % b == 0)
    return a / b;
  return a / b + 1;
}
ll mod = 1000000007;
const ll N = 200005;
vi adj[N];
ll vis[N];
void dfs(ll u) {
  vis[u] = 1;
  for (ll v : adj[u]) {
    if (vis[v] == 0) {
      dfs(v);
    }
  }
}
int main() {
  fast;
  ll n, m;
  cin >> n >> m;
  clr(vis);
  f(i, m) {
    ll x, y;
    cin >> x >> y;
    adj[x].pb(y);
    adj[y].pb(x);
  }
  ll c = 0;
  f1(i, n) {
    if (vis[i] == 0) {
      dfs(i);
      c++;
    }
  }
  cout << c - 1;
  return 0;
}


#include <bits/stdc++.h>

#define all(x) x.begin(), x.end()

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const long long MOD = 1e9 + 7;
const long long LINF = 1e18;
const int INF = 0x3f3f3f3f;
const int MAXN = 200050;

int fa[MAXN];
int find(int x) {
  if (fa[x] == x)
    return x;
  else
    return fa[x] = find(fa[x]);
}
void solve(int T) {
  int n, m, u, v;
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= n; i++) {
    fa[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    scanf("%d %d", &u, &v);
    fa[find(u)] = v;
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (fa[i] == i)
      ans++;
  }
  printf("%d", ans - 1);
}
signed main() {
  int t = 1;
  // ios::sync_with_stdio(false);
  // cin.tie(0);
  // scanf("%d",&t);
  for (int i = 1; i <= t; i++) {
    solve(i);
  }
}

#include <bits/stdc++.h>

#define all(x) x.begin(), x.end()

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const long long MOD = 1e9 + 7;
const long long LINF = 1e18;
const int INF = 0x3f3f3f3f;
const int MAXN = 200050;

int fa[MAXN];
int find(int x) {
  if (fa[x] == x)
    return x;
  else
    return fa[x] = find(fa[x]);
}
void solve(int T) {
  int n, m, u, v;
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= n; i++) {
    fa[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    scanf("%d %d", &u, &v);
    fa[find(u)] = find(v);
  }
  int ans = 0;
  for (int i = 1; i <= n; i++) {
    if (fa[i] == i)
      ans++;
  }
  printf("%d", ans - 1);
}
signed main() {
  int t = 1;
  // ios::sync_with_stdio(false);
  // cin.tie(0);
  // scanf("%d",&t);
  for (int i = 1; i <= t; i++) {
    solve(i);
  }
}


#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(void) {
  long long N, M;
  cin >> N >> M;
  vector<long long> A(M);
  vector<long long> B(M);
  for (long long i = 0; i < M; i++) {
    cin >> A[i] >> B[i];
  }
  UnionFind tree(N);
  for (long long i = 0; i < M; i++) {
    tree.unite(A[i], B[i]);
  }
  vector<long long> count(N, 0);
  long long ans = 0;
  for (long long i = 0; i < N; i++) {
    if (count[tree.root(i)] == 1) {
    } else {
      count[tree.root(i)] = 1;
      ans++;
    }
  }
  cout << ans - 1 << endl;
}

#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(void) {
  long long N, M;
  cin >> N >> M;
  vector<long long> A(M);
  vector<long long> B(M);
  for (long long i = 0; i < M; i++) {
    cin >> A[i] >> B[i];
  }
  UnionFind tree(N);
  for (long long i = 0; i < M; i++) {
    tree.unite(A[i] - 1, B[i] - 1);
  }
  vector<long long> count(N, 0);
  long long ans = 0;
  for (long long i = 0; i < N; i++) {
    if (count[tree.root(i)] == 1) {
    } else {
      count[tree.root(i)] = 1;
      ans++;
    }
  }
  cout << ans - 1 << endl;
}


////////////////<------------- Xorcestor_X -------------->////////////////
#include <algorithm>
#include <bits/stdc++.h>
#include <deque>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <iterator>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <utility>

using namespace std;
#define endl "\\n"
#define ll long long int
#define c(x)                                                                   \\
  ll x;                                                                        \\
  cin >> x
#define cc(x, y)                                                               \\
  ll x, y;                                                                     \\
  cin >> x >> y
#define ccc(x, y, z)                                                           \\
  ll x, y, z;                                                                  \\
  cin >> x >> y >> z
#define db long double
#define fast                                                                   \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);                                                              \\
  ios_base::sync_with_stdio(false)
#define inf 1e16
#define mod 1000000007
#define mod2 998244353
#define eps 1e-7
#define PI 3.1415926535897932385
#define pb push_back
#define bitc __builtin_popcountll
#define mp make_pair
#define ff first
#define ss second
#define all(ar) ar.begin(), ar.end()
#define len(x) (ll)(x).size()
#define fr(i, a, b) for (ll i = (a), _b = (b); i <= _b; i++)
#define rep(i, n) for (ll i = 0, _n = (n); i < _n; i++)
#define repr(i, n) for (ll i = n - 1; i >= 0; i--)
#define frr(i, a, b) for (ll i = (a), _b = (b); i >= _b; i--)
#define foreach(it, ar) for (auto it = ar.begin(); it != ar.end(); it++)
#define fill(ar, val) memset(ar, val, sizeof(ar))
#define print(arr, n)                                                          \\
  cout << " :: " << #arr << " :: " << endl;                                    \\
  rep(i, n) cout << arr[i] << " ";                                             \\
  cout << endl
#define print2(arr, n, m)                                                      \\
  cout << " :: " << #arr << " :: " << endl;                                    \\
  rep(i, n) {                                                                  \\
    rep(j, m) cout << arr[i][j] << " ";                                        \\
    cout << endl;                                                              \\
  }
#define dyn1(arr, n)                                                           \\
  ll *arr = new ll[n];                                                         \\
  rep(i, n) arr[i] = 0;
#define dyn2(arr, n, m)                                                        \\
  ll **arr = new ll *[(n)];                                                    \\
  rep(i, n) arr[i] = new ll[m];                                                \\
  rep(i, n) rep(j, m) arr[i][j] = 0
#define carr(arr, n) fr(i, 0, n - 1) cin >> arr[i]
#define carr2(arr, n, m) fr(i, 0, n - 1) fr(j, 0, m - 1) cin >> arr[i][j]
#define debug(a) cout << " :: " << #a << " == " << a << endl
#define debug2(a, b)                                                           \\
  cout << " :: " << #a << " == " << a << " :: " << #b << " == " << b << endl
#define watch(x) cerr << (#x) << " is " << (x) << endl
#define debugx(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)
#define time__(d)                                                              \\
  for (long blockTime = 0;                                                     \\
       (blockTime == 0 ? (blockTime = clock()) != 0 : false);                  \\
       debugx("%s time : %.4fs", d,                                            \\
              (double)(clock() - blockTime) / CLOCKS_PER_SEC))
typedef pair<ll, ll> pii;
typedef vector<pii> vii;
const ll N = 105;
typedef ll mt[N][N];

inline ll maxim(ll a, ll b) {
  if (a > b)
    return a;
  else
    return b;
}
inline ll minim(ll a, ll b) {
  if (a < b)
    return a;
  else
    return b;
}
inline bool equals(double a, double b) { return fabs(a - b) < 1e-9; }
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }

ll reduce(ll a, ll m) {
  a %= m;
  a += m;
  a %= m;
  return a;
}

ll Extended_gcd(ll a, ll b, ll &x, ll &y) {
  if (a == 0) {
    x = 0;
    y = 1;
    return b;
  } else {
    ll x1, y1;
    ll g = Extended_gcd(b % a, a, x1, y1);
    x = y1 - (b / a) * x1;
    y = x1;
    return g;
  }
}

bool leap(ll y) {
  if (y % 400 == 0)
    return true;
  if ((y % 4 == 0) && (y % 100 != 0))
    return true;
  return false;
}

ll isprime(ll n) {
  if (n == 1)
    return false;
  if (n == 2)
    return true;
  if (n % 2 == 0)
    return false;
  for (int i = 3; i * i <= n; i += 2) {
    if (n % i == 0)
      return false;
  }
  return true;
}

bool prime[2000010];
void SieveOfEratosthenes() {
  memset(prime, true, sizeof(prime));
  prime[1] = false;
  for (int p = 2; p * p <= 2000001; p++) {
    if (prime[p] == true) {
      for (int i = p * p; i <= 2000001; i += p)
        prime[i] = false;
    }
  }
}

// vector<ll> prime_vec[1000010];
// void modified_SieveOfEratosthenes()
// {
//     //prime[1]=false;
//     for (int p=2; p*p<=1000001; p++)
//     {
//         if (prime_vec[p].size()==0)
//         {
//             for (int i=p*2; i<=1000001; i += p)
//                 prime_vec[i].pb(p);
//         }
//     }
// }

////*** Various Exponentiation Functions ***////

ll binpow(ll a, ll b) {
  ll res = 1;
  while (b > 0) {
    if (b & 1)
      res = (res * a);
    a = (a * a);
    b >>= 1;
  }
  return res;
}

// ll result[1000010];
// ll tempo[1000010];
// ll per[1000010];
//
//
// void permutation_exp(ll n,ll x)
// {
//   fr(o,1,n)
//   {
//     result[o]=o;
//   }
//
//   while(x>0)
//   {
//     if(x&1)
//     {
//       fr(o,1,n)
//       {
//         tempo[o]=result[o];
//       }
//
//       fr(o,1,n)
//       {
//         result[o]=tempo[per[o]];
//       }
//     }
//
//     fr(o,1,n)
//     {
//       tempo[o]=per[o];
//     }
//
//     fr(o,1,n)
//     {
//       per[o]=tempo[tempo[o]];
//     }
//     x>>=1;
//   }
// }

ll len;
ll ultimate[110][110];

void matrix_exp(ll arr[100][110], ll n) {
  // ll len=4;
  ll res[110][110];
  rep(o, len) {
    rep(p, len) {
      if (o != p)
        res[o][p] = 0;
      else
        res[o][p] = 1;
    }
  }

  ll temp[110][110];

  while (n) {
    if (n & 1) {
      rep(x, len) {
        rep(y, len) { temp[x][y] = 0; }
      }

      fr(i, 0, len - 1) {
        fr(j, 0, len - 1) {
          fr(z, 0, len - 1) {
            temp[i][j] += reduce(res[i][z] * arr[z][j], mod);
          }
        }
      }

      fr(i, 0, len - 1) {
        fr(j, 0, len - 1) { res[i][j] = reduce(temp[i][j], mod); }
      }
    }

    rep(x, len) {
      rep(y, len) { temp[x][y] = 0; }
    }

    fr(i, 0, len - 1) {
      fr(j, 0, len - 1) {
        fr(z, 0, len - 1) {
          temp[i][j] += reduce(arr[i][z] * arr[z][j], mod);
          temp[i][j] %= mod;
        }
      }
    }

    fr(i, 0, len - 1) {
      fr(j, 0, len - 1) { arr[i][j] = reduce(temp[i][j], mod); }
    }

    n /= 2;
  }

  fr(i, 0, len - 1) {
    fr(j, 0, len - 1) { ultimate[i][j] = reduce(res[i][j], mod); }
  }
}

//
// ll modpow(ll a, ll b,ll x)
// {
//     ll res = 1;
//     while (b > 0)
//     {
//         if (b & 1) res = (res * a)%x;
//         a = (a * a)%x;
//         b >>= 1;
//     }
//     return res;
// }
//
// ////*** End of Various Exponentiation Functions ***////
//
// int modInverse(int a, int m)
// {
//     int m0 = m;
//     int y = 0, x = 1;
//     if (m == 1)
//       return 0;
//     while (a > 1)
//     {
//         // q is quotient
//         int q = a / m;
//         int t = m;
//         // m is remainder now, process same as
//         // Euclid's algo
//         m = a % m, a = t;
//         t = y;
//         // Update y and x
//         y = x - q * y;
//         x = t;
//     }
//     // Make x positive
//     if (x < 0)
//        x += m0;
//     return x;
// }
//
// ll int_sqrt(ll h)
// {
//   ll g=(ll)sqrt(h);
//   g++;
//   if(g*g==h) return g;
//   g--;
//   if(g*g==h) return g;
//   g--;
//   if(g*g==h) return g;
//   return -1;
// }
//
//
// ll lpf[1000001] = {};
//
// void precompute()
// {
//   fr(i,2,1000000)
//   {
//     if(!lpf[i])
//     {
//       for(ll j=i;j<=1000000;j+=i)
//       {
//         if(!lpf[j])
//         lpf[j]=i;
//       }
//     }
//   }
// }
//
// ll fact[2000010];
//
// void compute_fact()
// {
//   fact[0]=1;
//   fr(o,1,2000004)
//   {
//     fact[o]=(fact[o-1]*o)%mod;
//   }
// }
//
// ll ncr_mod(ll n,ll r)
// {
//   if(r==0||n==r) return 1;
//   else return
//   (((fact[n]*modInverse(fact[r],mod))%mod)*modInverse(fact[n-r],mod))%mod;
// }
//
//
// ll f(ll x)
// {
//   ll total=0;
//   return total;
// }
//
// ll ternary_search(ll l, ll r)
// {              //set the error limit here
//     while (r - l > 10)
//     {
//         ll m1 = l + (r - l) / 3;
//         ll m2 = r - (r - l) / 3;
//         ll f1 = f(m1);      //evaluates the function at m1
//         ll f2 = f(m2);      //evaluates the function at m2
//         if (f1 > f2)
//             l = m1;
//         else
//             r = m2;
//     }
//     ll mino=f(l);
//     ll mino_x=l;
//
//     fr(o,l,r)
//     {
//       if(f(o)<mino)
//       {
//         mino_x=o;
//         mino=f(o);
//       }
//     }
//     return f(mino_x);
// }
//
//
// // ll tree[2000010];
// // void build(ll* a,ll v,ll tl,ll tr)
// // {
// //   if(tl==tr)
// //   {
// //     tree[v]=((a[tl]==x)?1:0);
// //   }
// //   else
// //   {
// //     ll tm=(tl+tr)/2;
// //     build(a,v*2,tl,tm);
// //     build(a,v*2+1,tm+1,tr);
// //     tree[v]=tree[v*2]+tree[v*2+1];
// //   }
// // }
// //
// // ll query(ll v,ll tl,ll tr,ll l,ll r)
// // {
// //   if(l>r) return 0;
// //   if(l==tl&&r==tr) return tree[v];
// //   ll tm=(tl+tr)/2;
// //   return
// query(v*2,tl,tm,l,minim(r,tm))+query(v*2+1,tm+1,tr,maxim(l,tm+1),r);
// // }
// //
// //
// // void update(ll v,ll tl,ll tr,ll pos,ll new_val)
// // {
// //   if(tl==tr)
// //   {
// //     tree[v]=((arr[pos]==x)?1:0);
// //   }
// //   else
// //   {
// //     ll tm=(tl+tr)/2;
// //     if(pos<=tm) update(v*2,tl,tm,pos,new_val);
// //     else update(v*2+1,tm+1,tr,pos,new_val);
// //     tree[v]=tree[v*2]+tree[v*2+1];
// //   }
// // }
// //
// //
// // ll find_kth(ll v,ll tl,ll tr,ll k)
// // {
// //   if(k>tree[v]) return -1;
// //   if(tl==tr) return tl;
// //   ll tm=(tl+tr)/2;
// //   if(tree[v*2]>=k) return find_kth(v*2,tl,tm,k);
// //   else return find_kth(v*2+1,tm+1,tr,k-tree[v*2]);
// // }
//
//
// bool opt_prime(ll n)
// {
//   if(n<2000000) return prime[n];
//   else return isprime(n);
// }
//
//
// ll far[1000010];
// ll farsum[1000010];
//
// ll n,k;
// ll arr[1000010];
//
// ll func(ll gap)
// {
//   ll count=0;
//   ll pre=-inf;
//   rep(o,n)
//   {
//     if(arr[o]-pre>=gap)
//     {
//       pre=arr[o];
//       count++;
//     }
//   }
//
//   if(count>=k) return true;
//   else return false;
// }
//
// ll bs(ll low,ll high,ll target)
// {
//   while(low+4<high)
//   {
//     ll mid=(low+high)/2;
//     //increasing function with inclusion
//     if(func(mid))
//     {
//       low=mid;
//     }
//     else
//     {
//       high=mid-1;
//     }
//   }
//
//   frr(o,high,low)
//   {
//     if(func(o)) return o;
//   }
//
//   return -1;
// }
//
//
//
//
// ll lpfx(ll x)
// {
//   if(x%2==0) return 2;
//   else
//   {
//     for(int z=3;z*z<=x;z+=2)
//     {
//       if(x%z==0) return z;
//     }
//   }
//   return x;
// }
//
//
// ll fz(ll n)
// {
//   ll o=1;
//   while(o<=n)
//   {
//     o*=2;
//   }
//   return o;
// }

ll vis[100010] = {0};
vector<ll> adjx[100010];

ll dfs(ll node) {
  vis[node] = 1;
  rep(o, adjx[node].size()) {
    if (!vis[adjx[node][o]]) {
      dfs(adjx[node][o]);
    }
  }
}

int main() {
  fast;

  // #ifndef ONLINE_JUDGE
  // // for getting input from input.txt
  // freopen("input.txt", "r", stdin);
  // // for writing output to output.txt
  // freopen("output.txt", "w", stdout);
  // #endif

  cc(n, m);
  rep(o, m) {
    cc(a, b);
    adjx[a].pb(b);
    adjx[b].pb(a);
  }

  ll count = 0;

  fr(o, 1, n) {
    if (!vis[o]) {
      vis[o] = 1;
      count++;
      dfs(o);
    }
  }

  cout << count - 1 << endl;

  return 0;
}

////////////////<------------- Xorcestor_X -------------->////////////////
#include <algorithm>
#include <bits/stdc++.h>
#include <deque>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <iterator>
#include <map>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <utility>

using namespace std;
#define endl "\\n"
#define ll long long int
#define c(x)                                                                   \\
  ll x;                                                                        \\
  cin >> x
#define cc(x, y)                                                               \\
  ll x, y;                                                                     \\
  cin >> x >> y
#define ccc(x, y, z)                                                           \\
  ll x, y, z;                                                                  \\
  cin >> x >> y >> z
#define db long double
#define fast                                                                   \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);                                                              \\
  ios_base::sync_with_stdio(false)
#define inf 1e16
#define mod 1000000007
#define mod2 998244353
#define eps 1e-7
#define PI 3.1415926535897932385
#define pb push_back
#define bitc __builtin_popcountll
#define mp make_pair
#define ff first
#define ss second
#define all(ar) ar.begin(), ar.end()
#define len(x) (ll)(x).size()
#define fr(i, a, b) for (ll i = (a), _b = (b); i <= _b; i++)
#define rep(i, n) for (ll i = 0, _n = (n); i < _n; i++)
#define repr(i, n) for (ll i = n - 1; i >= 0; i--)
#define frr(i, a, b) for (ll i = (a), _b = (b); i >= _b; i--)
#define foreach(it, ar) for (auto it = ar.begin(); it != ar.end(); it++)
#define fill(ar, val) memset(ar, val, sizeof(ar))
#define print(arr, n)                                                          \\
  cout << " :: " << #arr << " :: " << endl;                                    \\
  rep(i, n) cout << arr[i] << " ";                                             \\
  cout << endl
#define print2(arr, n, m)                                                      \\
  cout << " :: " << #arr << " :: " << endl;                                    \\
  rep(i, n) {                                                                  \\
    rep(j, m) cout << arr[i][j] << " ";                                        \\
    cout << endl;                                                              \\
  }
#define dyn1(arr, n)                                                           \\
  ll *arr = new ll[n];                                                         \\
  rep(i, n) arr[i] = 0;
#define dyn2(arr, n, m)                                                        \\
  ll **arr = new ll *[(n)];                                                    \\
  rep(i, n) arr[i] = new ll[m];                                                \\
  rep(i, n) rep(j, m) arr[i][j] = 0
#define carr(arr, n) fr(i, 0, n - 1) cin >> arr[i]
#define carr2(arr, n, m) fr(i, 0, n - 1) fr(j, 0, m - 1) cin >> arr[i][j]
#define debug(a) cout << " :: " << #a << " == " << a << endl
#define debug2(a, b)                                                           \\
  cout << " :: " << #a << " == " << a << " :: " << #b << " == " << b << endl
#define watch(x) cerr << (#x) << " is " << (x) << endl
#define debugx(...) fprintf(stderr, __VA_ARGS__), fflush(stderr)
#define time__(d)                                                              \\
  for (long blockTime = 0;                                                     \\
       (blockTime == 0 ? (blockTime = clock()) != 0 : false);                  \\
       debugx("%s time : %.4fs", d,                                            \\
              (double)(clock() - blockTime) / CLOCKS_PER_SEC))
typedef pair<ll, ll> pii;
typedef vector<pii> vii;
const ll N = 105;
typedef ll mt[N][N];

inline ll maxim(ll a, ll b) {
  if (a > b)
    return a;
  else
    return b;
}
inline ll minim(ll a, ll b) {
  if (a < b)
    return a;
  else
    return b;
}
inline bool equals(double a, double b) { return fabs(a - b) < 1e-9; }
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }

ll reduce(ll a, ll m) {
  a %= m;
  a += m;
  a %= m;
  return a;
}

ll Extended_gcd(ll a, ll b, ll &x, ll &y) {
  if (a == 0) {
    x = 0;
    y = 1;
    return b;
  } else {
    ll x1, y1;
    ll g = Extended_gcd(b % a, a, x1, y1);
    x = y1 - (b / a) * x1;
    y = x1;
    return g;
  }
}

bool leap(ll y) {
  if (y % 400 == 0)
    return true;
  if ((y % 4 == 0) && (y % 100 != 0))
    return true;
  return false;
}

ll isprime(ll n) {
  if (n == 1)
    return false;
  if (n == 2)
    return true;
  if (n % 2 == 0)
    return false;
  for (int i = 3; i * i <= n; i += 2) {
    if (n % i == 0)
      return false;
  }
  return true;
}

bool prime[2000010];
void SieveOfEratosthenes() {
  memset(prime, true, sizeof(prime));
  prime[1] = false;
  for (int p = 2; p * p <= 2000001; p++) {
    if (prime[p] == true) {
      for (int i = p * p; i <= 2000001; i += p)
        prime[i] = false;
    }
  }
}

// vector<ll> prime_vec[1000010];
// void modified_SieveOfEratosthenes()
// {
//     //prime[1]=false;
//     for (int p=2; p*p<=1000001; p++)
//     {
//         if (prime_vec[p].size()==0)
//         {
//             for (int i=p*2; i<=1000001; i += p)
//                 prime_vec[i].pb(p);
//         }
//     }
// }

////*** Various Exponentiation Functions ***////

ll binpow(ll a, ll b) {
  ll res = 1;
  while (b > 0) {
    if (b & 1)
      res = (res * a);
    a = (a * a);
    b >>= 1;
  }
  return res;
}

// ll result[1000010];
// ll tempo[1000010];
// ll per[1000010];
//
//
// void permutation_exp(ll n,ll x)
// {
//   fr(o,1,n)
//   {
//     result[o]=o;
//   }
//
//   while(x>0)
//   {
//     if(x&1)
//     {
//       fr(o,1,n)
//       {
//         tempo[o]=result[o];
//       }
//
//       fr(o,1,n)
//       {
//         result[o]=tempo[per[o]];
//       }
//     }
//
//     fr(o,1,n)
//     {
//       tempo[o]=per[o];
//     }
//
//     fr(o,1,n)
//     {
//       per[o]=tempo[tempo[o]];
//     }
//     x>>=1;
//   }
// }

ll len;
ll ultimate[110][110];

void matrix_exp(ll arr[100][110], ll n) {
  // ll len=4;
  ll res[110][110];
  rep(o, len) {
    rep(p, len) {
      if (o != p)
        res[o][p] = 0;
      else
        res[o][p] = 1;
    }
  }

  ll temp[110][110];

  while (n) {
    if (n & 1) {
      rep(x, len) {
        rep(y, len) { temp[x][y] = 0; }
      }

      fr(i, 0, len - 1) {
        fr(j, 0, len - 1) {
          fr(z, 0, len - 1) {
            temp[i][j] += reduce(res[i][z] * arr[z][j], mod);
          }
        }
      }

      fr(i, 0, len - 1) {
        fr(j, 0, len - 1) { res[i][j] = reduce(temp[i][j], mod); }
      }
    }

    rep(x, len) {
      rep(y, len) { temp[x][y] = 0; }
    }

    fr(i, 0, len - 1) {
      fr(j, 0, len - 1) {
        fr(z, 0, len - 1) {
          temp[i][j] += reduce(arr[i][z] * arr[z][j], mod);
          temp[i][j] %= mod;
        }
      }
    }

    fr(i, 0, len - 1) {
      fr(j, 0, len - 1) { arr[i][j] = reduce(temp[i][j], mod); }
    }

    n /= 2;
  }

  fr(i, 0, len - 1) {
    fr(j, 0, len - 1) { ultimate[i][j] = reduce(res[i][j], mod); }
  }
}

//
// ll modpow(ll a, ll b,ll x)
// {
//     ll res = 1;
//     while (b > 0)
//     {
//         if (b & 1) res = (res * a)%x;
//         a = (a * a)%x;
//         b >>= 1;
//     }
//     return res;
// }
//
// ////*** End of Various Exponentiation Functions ***////
//
// int modInverse(int a, int m)
// {
//     int m0 = m;
//     int y = 0, x = 1;
//     if (m == 1)
//       return 0;
//     while (a > 1)
//     {
//         // q is quotient
//         int q = a / m;
//         int t = m;
//         // m is remainder now, process same as
//         // Euclid's algo
//         m = a % m, a = t;
//         t = y;
//         // Update y and x
//         y = x - q * y;
//         x = t;
//     }
//     // Make x positive
//     if (x < 0)
//        x += m0;
//     return x;
// }
//
// ll int_sqrt(ll h)
// {
//   ll g=(ll)sqrt(h);
//   g++;
//   if(g*g==h) return g;
//   g--;
//   if(g*g==h) return g;
//   g--;
//   if(g*g==h) return g;
//   return -1;
// }
//
//
// ll lpf[1000001] = {};
//
// void precompute()
// {
//   fr(i,2,1000000)
//   {
//     if(!lpf[i])
//     {
//       for(ll j=i;j<=1000000;j+=i)
//       {
//         if(!lpf[j])
//         lpf[j]=i;
//       }
//     }
//   }
// }
//
// ll fact[2000010];
//
// void compute_fact()
// {
//   fact[0]=1;
//   fr(o,1,2000004)
//   {
//     fact[o]=(fact[o-1]*o)%mod;
//   }
// }
//
// ll ncr_mod(ll n,ll r)
// {
//   if(r==0||n==r) return 1;
//   else return
//   (((fact[n]*modInverse(fact[r],mod))%mod)*modInverse(fact[n-r],mod))%mod;
// }
//
//
// ll f(ll x)
// {
//   ll total=0;
//   return total;
// }
//
// ll ternary_search(ll l, ll r)
// {              //set the error limit here
//     while (r - l > 10)
//     {
//         ll m1 = l + (r - l) / 3;
//         ll m2 = r - (r - l) / 3;
//         ll f1 = f(m1);      //evaluates the function at m1
//         ll f2 = f(m2);      //evaluates the function at m2
//         if (f1 > f2)
//             l = m1;
//         else
//             r = m2;
//     }
//     ll mino=f(l);
//     ll mino_x=l;
//
//     fr(o,l,r)
//     {
//       if(f(o)<mino)
//       {
//         mino_x=o;
//         mino=f(o);
//       }
//     }
//     return f(mino_x);
// }
//
//
// // ll tree[2000010];
// // void build(ll* a,ll v,ll tl,ll tr)
// // {
// //   if(tl==tr)
// //   {
// //     tree[v]=((a[tl]==x)?1:0);
// //   }
// //   else
// //   {
// //     ll tm=(tl+tr)/2;
// //     build(a,v*2,tl,tm);
// //     build(a,v*2+1,tm+1,tr);
// //     tree[v]=tree[v*2]+tree[v*2+1];
// //   }
// // }
// //
// // ll query(ll v,ll tl,ll tr,ll l,ll r)
// // {
// //   if(l>r) return 0;
// //   if(l==tl&&r==tr) return tree[v];
// //   ll tm=(tl+tr)/2;
// //   return
// query(v*2,tl,tm,l,minim(r,tm))+query(v*2+1,tm+1,tr,maxim(l,tm+1),r);
// // }
// //
// //
// // void update(ll v,ll tl,ll tr,ll pos,ll new_val)
// // {
// //   if(tl==tr)
// //   {
// //     tree[v]=((arr[pos]==x)?1:0);
// //   }
// //   else
// //   {
// //     ll tm=(tl+tr)/2;
// //     if(pos<=tm) update(v*2,tl,tm,pos,new_val);
// //     else update(v*2+1,tm+1,tr,pos,new_val);
// //     tree[v]=tree[v*2]+tree[v*2+1];
// //   }
// // }
// //
// //
// // ll find_kth(ll v,ll tl,ll tr,ll k)
// // {
// //   if(k>tree[v]) return -1;
// //   if(tl==tr) return tl;
// //   ll tm=(tl+tr)/2;
// //   if(tree[v*2]>=k) return find_kth(v*2,tl,tm,k);
// //   else return find_kth(v*2+1,tm+1,tr,k-tree[v*2]);
// // }
//
//
// bool opt_prime(ll n)
// {
//   if(n<2000000) return prime[n];
//   else return isprime(n);
// }
//
//
// ll far[1000010];
// ll farsum[1000010];
//
// ll n,k;
// ll arr[1000010];
//
// ll func(ll gap)
// {
//   ll count=0;
//   ll pre=-inf;
//   rep(o,n)
//   {
//     if(arr[o]-pre>=gap)
//     {
//       pre=arr[o];
//       count++;
//     }
//   }
//
//   if(count>=k) return true;
//   else return false;
// }
//
// ll bs(ll low,ll high,ll target)
// {
//   while(low+4<high)
//   {
//     ll mid=(low+high)/2;
//     //increasing function with inclusion
//     if(func(mid))
//     {
//       low=mid;
//     }
//     else
//     {
//       high=mid-1;
//     }
//   }
//
//   frr(o,high,low)
//   {
//     if(func(o)) return o;
//   }
//
//   return -1;
// }
//
//
//
//
// ll lpfx(ll x)
// {
//   if(x%2==0) return 2;
//   else
//   {
//     for(int z=3;z*z<=x;z+=2)
//     {
//       if(x%z==0) return z;
//     }
//   }
//   return x;
// }
//
//
// ll fz(ll n)
// {
//   ll o=1;
//   while(o<=n)
//   {
//     o*=2;
//   }
//   return o;
// }

ll vis[100010] = {0};
vector<ll> adjx[100010];

void dfs(ll node) {
  vis[node] = 1;
  rep(o, adjx[node].size()) {
    if (!vis[adjx[node][o]]) {
      dfs(adjx[node][o]);
    }
  }
}

int main() {
  fast;

  // #ifndef ONLINE_JUDGE
  // // for getting input from input.txt
  // freopen("input.txt", "r", stdin);
  // // for writing output to output.txt
  // freopen("output.txt", "w", stdout);
  // #endif

  cc(n, m);
  rep(o, m) {
    cc(a, b);
    adjx[a].pb(b);
    adjx[b].pb(a);
  }

  ll count = 0;

  fr(o, 1, n) {
    if (!vis[o]) {
      vis[o] = 1;
      count++;
      dfs(o);
    }
  }

  cout << count - 1 << endl;

  return 0;
}


#include <iostream>
#include <vector>
using namespace std;
int visited[100001];
vector<int> p[100001];
int num = 0;
void dfs(int curr, int prev) {
  if (visited[curr] == 1) {
    num++;
    return;
  }
  visited[curr] = 1;
  for (int i = 0; i < p[curr].size(); i++) {
    if (p[curr][i] != prev) {
      dfs(p[curr][i], curr);
    }
  }
  visited[curr] = 2;
}
int main() {
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int s, e;
    cin >> s >> e;
    p[s].push_back(e);
    p[e].push_back(s);
  }
  for (int i = 1; i <= n; i++) {
    if (visited[i] != 2) {
      dfs(i, 0);
    }
  }
  cout << (n - 1) - m - num << endl;
}

#include <iostream>
#include <vector>
using namespace std;
int visited[100001];
vector<int> p[100001];
int num = 0;
void dfs(int curr, int prev) {
  if (visited[curr] == 2) {
    return;
  }
  if (visited[curr] == 1) {
    num++;
    return;
  }
  visited[curr] = 1;
  for (int i = 0; i < p[curr].size(); i++) {
    if (p[curr][i] != prev) {
      dfs(p[curr][i], curr);
    }
  }
  visited[curr] = 2;
}
int main() {
  int n, m;
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    int s, e;
    cin >> s >> e;
    p[s].push_back(e);
    p[e].push_back(s);
  }
  for (int i = 1; i <= n; i++) {
    if (visited[i] != 2) {
      dfs(i, 0);
    }
  }
  cout << max((n - 1) - (m - num), 0) << endl;
}


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

int main() {
  int N, M;
  cin >> N >> M;
  vector<pair<int, int>> AB(N);
  vector<int> bucket(N, 0);
  for (int i = 0; i < M; i++) {
    int x, y;
    cin >> x >> y;
    AB.at(i) = make_pair(x, y);
    bucket.at(x - 1)++;
    bucket.at(y - 1)++;
  }
  vector<int> big_city;
  for (int i = 0; i < N; i++) {
    if (bucket.at(i) > 1) {
      big_city.push_back(i);
    }
  }
  for (int i = 0; big_city.size(); i++) {
    for (int j = 0; j < M; j++) {
      if (AB.at(j).first == big_city.at(i)) {
        bucket.at(AB.at(j).second - 1) = -1;
      } else if (AB.at(j).second == big_city.at(i)) {
        bucket.at(AB.at(j).first - 1) = -1;
      }
    }
  }

  int cnt0 = 0, cnt1 = 0, cntbig = 0;
  for (int i = 0; i < N; i++) {
    if (bucket.at(i) == 0)
      cnt0++;
    else if (bucket.at(i) == 1)
      cnt1++;
    else if (bucket.at(i) > 1)
      cntbig++;
  }

  cout << cnt0 + cnt1 / 2 + cntbig - 1 << endl;
}

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

using namespace std;

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

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

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

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

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

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

  UnionFind tree(N);

  for (int i = 0; i < M; i++) {
    int x, y;
    cin >> x >> y;
    tree.unite(x - 1, y - 1); // xの木とyの木を併合する
  }

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

  cout << cnt << endl;
  return 0;
}


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

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> vi(n, 0);
  vector<vector<int>> v(n, vector<int>(n, -1));
  map<int, vector<int>> mp;
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    mp[x].push_back(y);
    mp[y].push_back(x);
  }

  int cnt1 = 1;
  for (int i = 0; i < n; i++) {
    if (vi[i] != 0)
      continue;
    cnt1++;
    queue<int> q;
    q.push(i + 1);

    while (!q.empty()) {
      int size = q.size();
      for (int j = 0; j < size; j++) {
        int el = q.front();
        q.pop();
        vi[el - 1] = cnt1;
        for (int ii = 0; ii < mp[el].size(); ii++) {
          if (vi[mp[el][ii] - 1] == 0)
            q.push(mp[el][ii]);
        }
      }
    }
  }
  set<int> st;
  for (auto el : vi)
    st.insert(el);
  cout << st.size() - 1 << "\\n";
  return 0;
}

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

int main() {
  int n, m;
  cin >> n >> m;
  vector<int> vi(n, 0);
  map<int, vector<int>> mp;
  for (int i = 0; i < m; i++) {
    int x, y;
    cin >> x >> y;
    mp[x].push_back(y);
    mp[y].push_back(x);
  }

  int cnt1 = 1;
  for (int i = 0; i < n; i++) {
    if (vi[i] != 0)
      continue;
    cnt1++;
    queue<int> q;
    q.push(i + 1);

    while (!q.empty()) {
      int size = q.size();
      for (int j = 0; j < size; j++) {
        int el = q.front();
        q.pop();
        vi[el - 1] = cnt1;
        for (int ii = 0; ii < mp[el].size(); ii++) {
          if (vi[mp[el][ii] - 1] == 0)
            q.push(mp[el][ii]);
        }
      }
    }
  }
  set<int> st;
  for (auto el : vi)
    st.insert(el);
  cout << st.size() - 1 << "\\n";
  return 0;
}


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

// using namespace __gnu_pbds;
using namespace std;

#define fbo find_by_order
#define ook order_of_key
#define fi first
#define se second
#define long long long
typedef pair<int, int> ii;
typedef pair<int, ii> iii;
typedef vector<int> vi;
typedef vector<ii> vii;
// typedef tree<int, null_type, less<int>, rb_tree_tag,
// tree_order_statistics_node_update> ordered_set; mt19937_64
// rng(std::chrono::system_clock::now().time_since_epoch().count());

// /mnt/c/Users/vince/Desktop/POST-IOI/

int par[100003];
int parent(int u) { return par[u] = (u == par[u]) ? u : parent(par[u]); }
int gab(int u, int v) { par[parent(u)] = parent(v); }

int main() {
  // ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
  // freopen("input.in", "r", stdin);

  int n, m;
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= n; i++)
    par[i] = i;

  while (m--) {
    int u, v;
    scanf("%d %d", &u, &v);
    if (parent(u) == parent(v))
      continue;
    gab(u, v);
  }
  set<int> st;
  for (int i = 1; i <= n; i++)
    st.insert(parent(i));
  printf("%lu\\n", st.size() - 1);
}

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

// using namespace __gnu_pbds;
using namespace std;

#define fbo find_by_order
#define ook order_of_key
#define fi first
#define se second
#define long long long
typedef pair<int, int> ii;
typedef pair<int, ii> iii;
typedef vector<int> vi;
typedef vector<ii> vii;
// typedef tree<int, null_type, less<int>, rb_tree_tag,
// tree_order_statistics_node_update> ordered_set; mt19937_64
// rng(std::chrono::system_clock::now().time_since_epoch().count());

// /mnt/c/Users/vince/Desktop/POST-IOI/

int par[100003];
int parent(int u) { return par[u] = (u == par[u]) ? u : parent(par[u]); }
void gab(int u, int v) { par[parent(u)] = parent(v); }

int main() {
  // ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
  // freopen("input.in", "r", stdin);

  int n, m;
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= n; i++)
    par[i] = i;

  while (m--) {
    int u, v;
    scanf("%d %d", &u, &v);
    if (parent(u) == parent(v))
      continue;
    gab(u, v);
  }
  set<int> st;
  for (int i = 1; i <= n; i++)
    st.insert(parent(i));
  printf("%lu\\n", st.size() - 1);
}


#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr ll mod = 1e9 + 7;

struct UnionFind {
  vector<int> Parent;
  UnionFind(int n) { Parent = vector<int>(n, -1); }

  int root(int a) {
    if (Parent[a] < 0)
      return a;
    return root(Parent[a]);
  }

  int size(int a) { return -Parent[root(a)]; }

  bool connect(int a, int b) {
    if (root(a) == root(b))
      return false;
    if (size(a) < size(b))
      swap(a, b);
    Parent[root(a)] -= size(b);
    Parent[root(b)] = root(a);
    return true;
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind field(n);
  for (int i = 0; i < m; ++i) {
    int a, b;
    cin >> a >> b;
    field.connect(a, b);
  }
  int cnt = 0;
  for (int i = 0; i < n; ++i) {
    if (field.Parent[i] < 0) {
      cnt++;
    }
  }
  if (cnt == 0)
    cout << 0 << endl;
  else
    cout << cnt - 1 << endl;
}

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr ll mod = 1e9 + 7;

struct UnionFind {
  vector<int> Parent;
  UnionFind(int n) { Parent = vector<int>(n, -1); }

  int root(int a) {
    if (Parent[a] < 0)
      return a;
    return root(Parent[a]);
  }

  int size(int a) { return -Parent[root(a)]; }

  bool connect(int a, int b) {
    if (root(a) == root(b))
      return false;
    if (size(a) < size(b))
      swap(a, b);
    Parent[root(a)] -= size(b);
    Parent[root(b)] = root(a);
    return true;
  }
};

int main() {
  int n, m;
  cin >> n >> m;
  UnionFind field(n);
  for (int i = 0; i < m; ++i) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    field.connect(a, b);
  }
  int cnt = 0;
  for (int i = 0; i < n; ++i) {
    if (field.Parent[i] < 0) {
      cnt++;
    }
  }
  if (cnt == 0)
    cout << 0 << endl;
  else
    cout << cnt - 1 << endl;
}


#include <algorithm>
#include <bits/stdc++.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
using ll = long long;

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につける
  }
};

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

  UnionFind tree(N);

  for (int i = 0; i < M; i++) {
    int x, y;
    cin >> x >> y;
    tree.unite(x, y); // xの木とyの木を併合する
  }
  map<int, int> mp;
  for (int i = 0; i < N; i++) {
    int a = tree.root(i);
    mp[a]++;
  }
  int ans = 0;
  for (int i = 0; i < N; i++) {
    if (mp[i] >= 1)
      ans++;
  }
  cout << ans - 1 << endl;
  return 0;
}

#include <algorithm>
#include <bits/stdc++.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
using ll = long long;

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につける
  }
};

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

  UnionFind tree(N);

  for (int i = 0; i < M; i++) {
    int x, y;
    cin >> x >> y;
    x--;
    y--;
    tree.unite(x, y); // xの木とyの木を併合する
  }
  map<int, int> mp;
  for (int i = 0; i < N; i++) {
    int a = tree.root(i);
    mp[a]++;
  }
  int ans = 0;
  for (int i = 0; i < N; i++) {
    if (mp[i] >= 1)
      ans++;
  }
  cout << ans - 1 << endl;
  return 0;
}


#include <cstdio>
#include <iostream>
#include <vector>
#define maxn 1000
using namespace std;

vector<int> G[maxn];
bool vis[maxn] = {false};

int m, n;

void dfs(int v) {
  vis[v] = true;
  for (int i = 0; i < G[v].size(); i++) {
    if (vis[G[v][i]] == false) {
      dfs(G[v][i]);
    }
  }
}

int main() {
  scanf("%d %d", &m, &n);
  for (int i = 0; i < n; i++) {
    int a, b;
    scanf("%d %d", &a, &b);
    G[a].push_back(b);
    G[b].push_back(a);
  }

  int block = 0;
  for (int i = 1; i <= m; i++) {
    if (vis[i] == false) {
      dfs(i);
      block++;
    }
  }

  printf("%d", block - 1);

  return 0;
}

#include <cstdio>
#include <iostream>
#include <vector>
#define maxn 200000
using namespace std;

vector<int> G[maxn];
bool vis[maxn] = {false};

int m, n;

void dfs(int v) {
  vis[v] = true;
  for (int i = 0; i < G[v].size(); i++) {
    if (vis[G[v][i]] == false) {
      dfs(G[v][i]);
    }
  }
}

int main() {
  scanf("%d %d", &m, &n);
  for (int i = 0; i < n; i++) {
    int a, b;
    scanf("%d %d", &a, &b);
    G[a].push_back(b);
    G[b].push_back(a);
  }

  int block = 0;
  for (int i = 1; i <= m; i++) {
    if (vis[i] == false) {
      dfs(i);
      block++;
    }
  }

  printf("%d", block - 1);

  return 0;
}


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

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);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    uf.unite(a, b);
  }
  int cnt = 0;
  rep(i, n) {
    if (!uf.same(i, i + 1))
      cnt++;
  }
  cnt--;
  cout << cnt << 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;

int main() {
  int n, m;
  cin >> n >> m;
  vector<vector<int>> G(n);
  rep(i, m) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    G[a].push_back(b);
    G[b].push_back(a);
  }
  int cnt = 0;
  vector<int> dist(n, -1);
  rep(sv, n) {
    if (dist[sv] != -1)
      continue;
    queue<int> q;
    dist[sv] = 0;
    q.push(sv);
    while (!q.empty()) {
      int v = q.front();
      q.pop();
      for (int nv : G[v]) {
        if (dist[nv] != -1)
          continue;
        dist[nv] = dist[v] + 1;
        q.push(nv);
      }
    }
    cnt++;
  }
  int ans = cnt - 1;
  cout << ans << endl;
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define F first
#define S second
#define IOS                                                                    \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);
#define mod 1e7
vector<ll> g[100005];
ll vis[100005];
ll n, m;
void dfs(ll u, ll pt) {
  vis[u] = 1;
  for (auto it : g[u]) {
    if (it == pt)
      continue;
    dfs(it, u);
  }
}
void solve() {
  // ll n,m;
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    g[i].clear();
    vis[i] = 0;
  }
  // vector<ll> g[n+1];
  while (m--) {
    ll a, b;
    cin >> a >> b;
    g[a].pb(b);
    g[b].pb(a);
  }
  int cnt = 0;
  for (int i = 1; i <= n; i++) {
    if (vis[i] == 0) {
      dfs(i, -1);
      cnt++;
    }
  }
  cout << cnt - 1 << '\\n';
}
int main() {
  IOS;
  int tt;
  tt = 1;
  // cin>>tt;
  int i = 1;
  while (tt--) {
    // cout<<"tc "<<i<<" ";
    solve();
    i++;
  }
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
#define F first
#define S second
#define IOS                                                                    \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL);                                                               \\
  cout.tie(NULL);
#define mod 1e7
vector<ll> g[100005];
ll vis[100005];

void dfs(ll u, ll pt) {
  vis[u] = 1;
  for (auto it : g[u]) {
    if (it == pt || vis[it])
      continue;
    dfs(it, u);
  }
}
void solve() {
  ll n, m;
  cin >> n >> m;
  for (int i = 0; i <= n; i++) {
    g[i].clear();
    vis[i] = 0;
  }
  // vector<ll> g[n+1];
  for (int i = 0; i < m; i++) {
    ll a, b;
    cin >> a >> b;
    // cout<<"next0";
    g[a].pb(b);
    g[b].pb(a);
  }
  // cout<<"hi0\\n";
  ll cnt = 0;
  for (ll i = 1; i <= n; i++) {

    if (vis[i] == 0) { // cout<<"his";
      dfs(i, -1);
      // cout<<"hi\\n";
      cnt++;
    }
  }
  // cout<<cnt;
  if (cnt == 0) {
    cout << "0\\n";
    return;
  }
  cout << cnt - 1 << '\\n';
}
int main() {
  // IOS;
  int tt;
  tt = 1;
  // cin>>tt;
  int i = 1;
  while (tt--) {
    // cout<<"tc "<<i<<" ";
    solve();
    i++;
  }
  return 0;
}


#include <bits/stdc++.h>
#define f first
#define s second
#define pb push_back
#define M (L + R) / 2
#define N 500009
#define inf 99999999999
#define ll long long
#define mod 1000000007
#define sz size();
using namespace std;
ll n, m, a, b, parent[N], child[N];

ll Parent(ll x) {
  if (x != parent[x]) {
    parent[x] = Parent(parent[x]);
    return parent[x];
  }
}

void merge(ll x, ll y) {
  if (Parent(x) == Parent(y))
    return;
  if (child[x] < child[y])
    swap(x, y);
  parent[y] = x;
  child[x] += child[y];
  return;
}

int main() {

  ios::sync_with_stdio(0);

  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    child[i] = 1;
    parent[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    cin >> a >> b;
    merge(a, b);
  }
  //	ll ans=0;
  //	for(int i=1;i<=n;i++){
  //		if(child[parent[i]]!=0)ans++,child[parent[i]]=0;
  //	}
  //	cout<<ans-1<<endl;
  ll ans = max(n - 1 - m, 0ll);
  cout << ans << endl;
  return 0;
}

#include <bits/stdc++.h>
#define f first
#define s second
#define pb push_back
#define M (L + R) / 2
#define N 500009
#define inf 99999999999
#define ll long long
#define mod 1000000007
#define sz size();
using namespace std;
ll n, m, a, b, parent[N], child[N];

ll Parent(ll x) {
  if (x != parent[x])
    parent[x] = Parent(parent[x]);
  return parent[x];
}

void merge(ll x, ll y) {
  x = Parent(x);
  y = Parent(y);
  if (x == y)
    return;
  if (child[x] < child[y])
    swap(x, y);
  parent[y] = x;
  child[x] += child[y];
  return;
}

int main() {

  ios::sync_with_stdio(0);

  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    child[i] = 1;
    parent[i] = i;
  }
  for (int i = 1; i <= m; i++) {
    cin >> a >> b;
    merge(a, b);
  }
  ll ans = -1;
  for (int i = 1; i <= n; i++) {
    if (child[Parent(i)] != 0)
      ans++, child[Parent(i)] = 0;
  }
  cout << ans << 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 int
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL)
#define all(x) (x).begin(), (x).end()

void dfs(vector<vector<ll>> v, ll p, vector<bool> &vis) {

  vis[p] = true;

  for (auto it = v[p].begin(); it != v[p].end(); ++it) {
    if (vis[*it] == false)
      dfs(v, *it, vis);
  }
}

int main() {

  ll n, m;
  cin >> n >> m;

  vector<vector<ll>> v(n + 1);

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

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

  ll ans = 0;

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

  cout << ans - 1 << endl;
}

#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 int
#define fast                                                                   \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL)
#define all(x) (x).begin(), (x).end()

void dfs(vector<vector<ll>> &v, ll p, vector<bool> &vis) {

  vis[p] = true;

  for (auto it = v[p].begin(); it != v[p].end(); ++it) {
    if (vis[*it] == false)
      dfs(v, *it, vis);
  }
}

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

  vector<vector<ll>> v(n + 1);

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

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

  ll ans = 0;

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

  cout << ans - 1 << endl;
}


#include <algorithm>
#include <array>
#include <assert.h>
#include <bitset>
#include <chrono>
#include <cmath>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <stdio.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

using namespace std;

template <typename M, typename N>
istream &operator>>(istream &is, pair<M, N> &p) {
  is >> p.first >> p.second;
  return is;
}
template <typename M> istream &operator>>(istream &is, vector<M> &v) {
  for (auto &it : v)
    is >> it;
  return is;
}
template <typename M> istream &operator>>(istream &is, vector<vector<M>> &v) {
  for (auto &it : v)
    is >> it;
  return is;
}
template <typename M, typename N>
ostream &operator<<(ostream &os, const pair<M, N> &p) {
  os << p.first << ' ' << p.second << '\\n';
  return os;
}
template <typename M> ostream &operator<<(ostream &os, const vector<M> &v) {
  for (auto it : v)
    os << it << ' ';
  cout << '\\n';
  return os;
}
template <typename M>
ostream &operator<<(ostream &os, const vector<vector<M>> &v) {
  for (auto it : v)
    os << it;
  return os;
}

#define pb push_back
#define fi first
#define se second
#define all(x) (x).begin(), (x).end()
#define mn(x) *min_element((x).begin(), (x).end())
#define mx(x) *max_element((x).begin(), (x).end())
#define acc(x) accumulate((x).begin(), (x).end(), 0ll)
#define eb emplace_back
#define el '\\n'
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
typedef vector<ll> vll;
typedef vector<int> vi;
typedef vector<pii> vpi;
typedef vector<pll> vpll;
typedef vector<vi> vvi;
typedef vector<vll> vvll;
typedef vector<bool> vb;
typedef vector<string> vs;

const ld pi = acos(-1);
const ll inf = (ll)1e9 + 10;
// const int mod=1000000007;
const int mod = 998244353;

int n, m, k;
const int N = 2e5 + 10;

vvi adj(N);
vb mark(N);

void dfs(int u, int p) {
  mark[u] = true;
  for (auto it : adj[u]) {
    if (it != p) {
      dfs(it, u);
    }
  }
  return;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int n, m;
  cin >> n >> m;
  for (int i = 0, u, v; i < m; i++) {
    cin >> u >> v;
    adj[--u].pb(--v);
    adj[v].pb(u);
  }
  int ans = -1;
  for (int i = 0; i < n; i++) {
    if (!mark[i]) {
      ans++;
      dfs(i, i);
    }
  }
  cout << ans << el;

  return 0;
}

#include <algorithm>
#include <array>
#include <assert.h>
#include <bitset>
#include <chrono>
#include <cmath>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <stdio.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

using namespace std;

template <typename M, typename N>
istream &operator>>(istream &is, pair<M, N> &p) {
  is >> p.first >> p.second;
  return is;
}
template <typename M> istream &operator>>(istream &is, vector<M> &v) {
  for (auto &it : v)
    is >> it;
  return is;
}
template <typename M> istream &operator>>(istream &is, vector<vector<M>> &v) {
  for (auto &it : v)
    is >> it;
  return is;
}
template <typename M, typename N>
ostream &operator<<(ostream &os, const pair<M, N> &p) {
  os << p.first << ' ' << p.second << '\\n';
  return os;
}
template <typename M> ostream &operator<<(ostream &os, const vector<M> &v) {
  for (auto it : v)
    os << it << ' ';
  cout << '\\n';
  return os;
}
template <typename M>
ostream &operator<<(ostream &os, const vector<vector<M>> &v) {
  for (auto it : v)
    os << it;
  return os;
}

#define pb push_back
#define fi first
#define se second
#define all(x) (x).begin(), (x).end()
#define mn(x) *min_element((x).begin(), (x).end())
#define mx(x) *max_element((x).begin(), (x).end())
#define acc(x) accumulate((x).begin(), (x).end(), 0ll)
#define eb emplace_back
#define el '\\n'
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
typedef vector<ll> vll;
typedef vector<int> vi;
typedef vector<pii> vpi;
typedef vector<pll> vpll;
typedef vector<vi> vvi;
typedef vector<vll> vvll;
typedef vector<bool> vb;
typedef vector<string> vs;

const ld pi = acos(-1);
const ll inf = (ll)1e9 + 10;
// const int mod=1000000007;
const int mod = 998244353;

int n, m, k;
const int N = 2e5 + 10;

vvi adj(N);
vb mark(N);

void dfs(int u, int p) {
  mark[u] = true;
  for (auto it : adj[u]) {
    if (it != p && !mark[it]) {
      dfs(it, u);
    }
  }
  return;
}

int main() {
  ios_base::sync_with_stdio(false);
  cin.tie(NULL);
  int n, m;
  cin >> n >> m;
  for (int i = 0, u, v; i < m; i++) {
    cin >> u >> v;
    adj[--u].pb(--v);
    adj[v].pb(u);
  }
  int ans = -1;
  for (int i = 0; i < n; i++) {
    if (!mark[i]) {
      ans++;
      dfs(i, i);
    }
  }
  cout << ans << el;

  return 0;
}


#pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using P = pair<ll, ll>;
template <class T> using V = vector<T>;
#define fi first
#define se second
#define all(v) (v).begin(), (v).end()
const ll inf = (1e18);
// const ll mod=998244353;
const ll mod = 1000000007;
const vector<int> dy = {-1, 0, 1, 0}, dx = {0, -1, 0, 1};
ll GCD(ll a, ll b) { return b ? GCD(b, a % b) : a; }
ll LCM(ll c, ll d) { return c / GCD(c, d) * d; }
struct __INIT {
  __INIT() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(20);
  }
} __init;
template <class T> bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> bool chmin(T &a, const T &b) {
  if (a > b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> void debag(const vector<T> &a) {
  cerr << "debag :";
  for (auto v : a)
    cerr << v << " ";
  cerr << "\\n";
}
template <class T> void print(const vector<T> &a) {
  for (auto v : a)
    cout << v << " ";
  cout << "\\n";
}
class UF {
public:
  vector<int> par, num;
  int find(int v) { return (par[v] == v) ? v : (par[v] = find(par[v])); }
  explicit UF(int N) : par(N), num(N, 1) { iota(all(par), 0); }
  void unite(int u, int v) {
    u = find(u), v = find(v);
    if (u == v)
      return;
    if (num[u] < num[v])
      swap(u, v);
    num[u] += num[v];
    par[v] = u;
  }
  bool same(int u, int v) { return find(u) == find(v); }
  bool ispar(int v) { return v = find(v); }
  int size(int v) { return num[find(v)]; }
};
int main() {
  int n, m;
  cin >> n >> m;
  UF uf(n);
  int a, b, ans = 0;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    uf.unite(a, b);
  }
  for (int i = 0; i < n; i++) {
    if (uf.find(i) != i)
      continue;
    ans++;
  }
  cout << ans - 1 << "\\n";
}

#pragma GCC optimize("O3")
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using P = pair<ll, ll>;
template <class T> using V = vector<T>;
#define fi first
#define se second
#define all(v) (v).begin(), (v).end()
const ll inf = (1e18);
// const ll mod=998244353;
const ll mod = 1000000007;
const vector<int> dy = {-1, 0, 1, 0}, dx = {0, -1, 0, 1};
ll GCD(ll a, ll b) { return b ? GCD(b, a % b) : a; }
ll LCM(ll c, ll d) { return c / GCD(c, d) * d; }
struct __INIT {
  __INIT() {
    cin.tie(0);
    ios::sync_with_stdio(false);
    cout << fixed << setprecision(20);
  }
} __init;
template <class T> bool chmax(T &a, const T &b) {
  if (a < b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> bool chmin(T &a, const T &b) {
  if (a > b) {
    a = b;
    return 1;
  }
  return 0;
}
template <class T> void debag(const vector<T> &a) {
  cerr << "debag :";
  for (auto v : a)
    cerr << v << " ";
  cerr << "\\n";
}
template <class T> void print(const vector<T> &a) {
  for (auto v : a)
    cout << v << " ";
  cout << "\\n";
}
class UF {
public:
  vector<int> par, num;
  int find(int v) { return (par[v] == v) ? v : (par[v] = find(par[v])); }
  explicit UF(int N) : par(N), num(N, 1) { iota(all(par), 0); }
  void unite(int u, int v) {
    u = find(u), v = find(v);
    if (u == v)
      return;
    if (num[u] < num[v])
      swap(u, v);
    num[u] += num[v];
    par[v] = u;
  }
  bool same(int u, int v) { return find(u) == find(v); }
  bool ispar(int v) { return v = find(v); }
  int size(int v) { return num[find(v)]; }
};
int main() {
  int n, m;
  cin >> n >> m;
  UF uf(n);
  int a, b, ans = 0;
  for (int i = 0; i < m; i++) {
    cin >> a >> b;
    a--;
    b--;
    uf.unite(a, b);
  }
  for (int i = 0; i < n; i++) {
    if (uf.find(i) != i)
      continue;
    ans++;
  }
  cout << ans - 1 << "\\n";
}


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

void dfs(int source, vector<int> adj[], vector<bool> &visited) {
  visited[source] = true;

  for (auto v : adj[source]) {
    if (!visited[v]) {
      dfs(v, adj, visited);
    }
  }
}

int main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);

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

  vector<int> adj[n];

  while (m--) {
    int a, b;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }

  vector<bool> visited(n, false);
  int components = 0;

  for (int i = 0; i < n; i++) {
    if (!visited[i]) {
      components++;
      dfs(i, adj, visited);
    }
  }

  cout << components - 1 << '\\n';

  return 0;
}

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

void dfs(int source, vector<int> adj[], vector<bool> &visited) {
  visited[source] = true;

  for (auto v : adj[source]) {
    if (!visited[v]) {
      dfs(v, adj, visited);
    }
  }
}

int main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  // cout.tie(0);

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

  vector<int> adj[n + 1];

  while (m--) {
    int a = 0, b = 0;
    cin >> a >> b;
    adj[a].push_back(b);
    adj[b].push_back(a);
  }

  vector<bool> visited(n + 1, false);
  int components = 0;

  for (int i = 1; i <= n; i++) {
    if (!visited[i]) {
      components++;
      dfs(i, adj, visited);
    }
  }

  cout << components - 1 << "\\n";

  return 0;
}