Problem:
Problem D:
King Slime
There is a grid of size
W
×
H
surrounded by walls. Some cells in the grid are occupied by slimes. The slimes want to unite with each other and become a "King Slime".
In each move, a slime can move to east, west, south and north direction until it enters a cell occupied by another slime or hit the surrounding wall. If two slimes come together, they unite and become a new slime.
Your task is write a program which calculates the minimum number of moves that all the slimes unite and become a King Slime. Suppose slimes move one by one and they never move simultaneously.
Input
The first line contains three integers
N
(2 ≤
N
≤ 40,000),
W
and
H
(1 ≤
W
,
H
≤ 100,000), which denote the
number of slimes, the width and the height of the grid respectively.
The following
N
lines describe the initial coordinates of the slimes. The
i
-th line contains two integers
x
i
(1 ≤
x
i
≤
W
) and
y
i
(1 ≤
y
i
≤
H
), which indicate the coordinates of the
i
-th slime . All the coordinates are 1-based.
You may assume that each cell is occupied by at most one slime initially.
Output
Output the minimum number of moves that all the slimes unite and become a King Slime.
Sample Input 1
4 3 3
1 1
1 3
3 1
3 3
Output for the Sample Input 1
3
Sample Input 2
2 3 3
2 2
3 3
Output for the Sample Input 2
2
Sample Input 3
2 4 4
2 2
3 3
Output for the Sample Input 3
3
Sample Input 4
2 4 4
2 2
2 3
Output for the Sample Input 4
1

Submitted solutions:

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

constexpr int INF = 1e9;

class union_find {
public:
    union_find(int N)
        : par(N, -1)
    {}

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

    bool unite(int x, int y) {
        x = root(x), y = root(y);
        if(x == y) {
            return false;
        }
        if(par[x] < par[y]) {
            par[x] += par[y];
            par[y] = x;
        } else {
            par[y] += par[x];
            par[x] = y;
        }
        return true;
    }
    
    int size(int x) {
        return -par[root(x)];
    }
private:
    vector<int> par;
};

int main() {
    int N, W, H;
    cin >> N >> W >> H;
    vector<vector<int>> sx(W), sy(H);
    union_find uf(N);
    bool f = false;
    for(int i=0; i<N; ++i) {
        int x, y;
        cin >> x >> y;
        x--; y--;
        sx[x].push_back(i);
        sy[y].push_back(i);
        f |= x == 0 || x == W-1 || y == 0 || y == H-1;
    }
    for(int i=0; i<W; ++i) {
        for(int j=0; j<(int)sx[i].size()-1; ++j) {
            uf.unite(sx[i][j], sx[i][j+1]);
        }
    }
    for(int i=0; i<H; ++i) {
        for(int j=0; j<(int)sy[i].size()-1; ++j) {
            uf.unite(sy[i][j], sy[i][j+1]);
        }
    }
    int res = 0;
    int cnt = 0;
    for(int i=0; i<N; ++i) {
        if(uf.root(i) == i) {
            res += uf.size(i) - 1;
            cnt++;
        }
    }
    if(cnt != 1) {
        res += cnt * 2 - 1 - f;
    }
    cout << res << endl;
}
accuracy: 1
time_ms: 10
memory_kb: 9892
final_rank: 2

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

using VS = vector<string>;    using LL = long long;
using VI = vector<int>;       using VVI = vector<VI>;
using PII = pair<int, int>;   using PLL = pair<LL, LL>;
using VL = vector<LL>;        using VVL = vector<VL>;

#define ALL(a)  begin((a)),end((a))
#define RALL(a) (a).rbegin(), (a).rend()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define SZ(a) int((a).size())
#define SORT(c) sort(ALL((c)))
#define RSORT(c) sort(RALL((c)))
#define UNIQ(c) (c).erase(unique(ALL((c))), end((c)))
#define FOR(i, s, e) for (int(i) = (s); (i) < (e); (i)++)
#define FORR(i, s, e) for (int(i) = (s); (i) > (e); (i)--)
#define debug(x) cerr << #x << ": " << x << endl
const int INF = 1e9;                          const LL LINF = 1e16;
const LL MOD = 1000000007;                    const double PI = acos(-1.0);
int DX[8] = { 0, 0, 1, -1, 1, 1, -1, -1 };    int DY[8] = { 1, -1, 0, 0, 1, -1, 1, -1 };

/* -----  2018/04/20  Problem: AOJ 2382 / Link: http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2382  ----- */
/* ------問題------



-----問題ここまで----- */
/* -----解説等-----



----解説ここまで---- */

struct UnionFind {
	vector<int> data;
	UnionFind(int n) { data.assign(n, -1); }
	bool unionSet(int x, int y) {
		x = root(x); y = root(y);
		if (x != y) {
			if (data[y] < data[x]) swap(x, y);
			data[x] += data[y]; data[y] = x;
		}
		return x != y;
	}
	bool same(int x, int y) { return root(x) == root(y); }
	int root(int x) { return data[x] < 0 ? x : data[x] = root(data[x]); }
	int size(int x) { return -data[root(x)]; }
};

LL N, W, H;

LL ans = 0LL;

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

	cin >> N >> H >> W;

	VI x(N), y(N);
	int Wall = 0;
	FOR(i, 0, N) {
		cin >> x[i] >> y[i];
		if (x[i] == 1 || y[i] == 1 || x[i] == W || y[i] == H)Wall = 1;
	}
	UnionFind uf(N);

	map<int, int>Xs;
	map<int, int>Ys;
	vector<bool> usex(1000000, 0);
	vector<bool> usey(1000000, 0);
	FOR(i, 0, N) { // mergeを
		if (usex[x[i]])uf.unionSet(Xs[x[i]], i);
		Xs[x[i]] = i;
		if (usey[y[i]])uf.unionSet(Ys[y[i]], i);
		Ys[y[i]] = i;
		usex[x[i]] = 1;
		usey[y[i]] = 1;
	}


	set<int>se;
	FOR(i, 0, N) {
		se.insert(uf.root(i));
	}
	int sz = SZ(se);
	if (sz != 1) {
		ans = N - 1 + sz - Wall;
	}
	else {
		ans = N - 1;
	}
	cout << ans << "\n";

	return 0;
}
accuracy: 0.25842696629213485
time_ms: 20
memory_kb: 8052
final_rank: 16

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

using VS = vector<string>;    using LL = long long;
using VI = vector<int>;       using VVI = vector<VI>;
using PII = pair<int, int>;   using PLL = pair<LL, LL>;
using VL = vector<LL>;        using VVL = vector<VL>;

#define ALL(a)  begin((a)),end((a))
#define RALL(a) (a).rbegin(), (a).rend()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define SZ(a) int((a).size())
#define SORT(c) sort(ALL((c)))
#define RSORT(c) sort(RALL((c)))
#define UNIQ(c) (c).erase(unique(ALL((c))), end((c)))
#define FOR(i, s, e) for (int(i) = (s); (i) < (e); (i)++)
#define FORR(i, s, e) for (int(i) = (s); (i) > (e); (i)--)
#define debug(x) cerr << #x << ": " << x << endl
const int INF = 1e9;                          const LL LINF = 1e16;
const LL MOD = 1000000007;                    const double PI = acos(-1.0);
int DX[8] = { 0, 0, 1, -1, 1, 1, -1, -1 };    int DY[8] = { 1, -1, 0, 0, 1, -1, 1, -1 };

/* -----  2018/04/20  Problem: AOJ 2382 / Link: http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2382  ----- */
/* ------問題------



-----問題ここまで----- */
/* -----解説等-----



----解説ここまで---- */

struct UnionFind {
	vector<int> data;
	UnionFind(int n) { data.assign(n, -1); }
	bool unionSet(int x, int y) {
		x = root(x); y = root(y);
		if (x != y) {
			if (data[y] < data[x]) swap(x, y);
			data[x] += data[y]; data[y] = x;
		}
		return x != y;
	}
	bool same(int x, int y) { return root(x) == root(y); }
	int root(int x) { return data[x] < 0 ? x : data[x] = root(data[x]); }
	int size(int x) { return -data[root(x)]; }
};

LL N, W, H;

LL ans = 0LL;

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

	cin >> N >> H >> W;

	VI x(N), y(N);
	int Wall = 0;
	FOR(i, 0, N) {
		cin >> x[i] >> y[i];
		if (x[i] == 1 || y[i] == 1 || x[i] == W || y[i] == H)Wall = 1;
	}
	UnionFind uf(N);

	VI Xs(2*W + 1, -1);
	VI Ys(2*H + 1, -1);
	FOR(i, 0, N) { // mergeを
		if (Xs[x[i]] != -1)uf.unionSet(Xs[x[i]], i);
		Xs[x[i]] = i;
		if (Ys[y[i]] != -1)uf.unionSet(Ys[y[i]], i);
		Ys[y[i]] = i;
	}


	set<int>se;
	FOR(i, 0, N) {
		se.insert(uf.root(i));
	}
	int sz = SZ(se);
	if (sz != 1) {
		ans = N - 1 + sz - Wall;
	}
	else {
		ans = N - 1;
	}
	cout << ans << "\n";

	return 0;
}
accuracy: 0.1348314606741573
time_ms: 10
memory_kb: 6068
final_rank: 18

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

class UFT {
public:
  vector<int> parent;
  vector<int> rank;
  vector<int> size;
  UFT(int n){
    parent = vector<int>(n);
    rank = vector<int>(n,0);
    size = vector<int>(n,1);
    for(int i = 0; i < n; i++)
      parent[i] = i;
  }
  int find_root(int x){
    if ( parent[x] == x ) return x;
    return parent[x] = find_root( parent[x] );
  }
  void unite(int x, int y){
    int u = find_root(x);
    int v = find_root(y);
    if ( u == v ) return;
    if ( rank[u] < rank[v] ){
      parent[u] = v;
      size[v] += size[u];
    }else{
      parent[v] = u;
      size[u] += size[v];
      if ( rank[u] == rank[v] ) rank[x]++;
    }
  }
  bool is_same ( int x, int y ){
    return find_root(x) == find_root(y);
  }
  int size_of ( int x ){
    return size[ find_root(x) ];
  }
};

typedef long long Cost;
class edge{
public:
  int u,v;;
  Cost cost;
  edge(int u, int v, Cost c) : u(u), v(v), cost(c) { ; }
  bool operator< (const edge &e) const {
    return cost < e.cost;
  }
};
class SpanningTree{
public:
  int V;
  vector< edge > edges;
  SpanningTree(int n) : V(n) {; }
  void add_edge(int v, int u, Cost c){
    edges.push_back( edge(v,u,c) );
  }
  Cost kruskal(){
    sort( edges.begin(), edges.end() );
    UFT uft(V);
    Cost ret = 0;
    for(int i = 0; i < edges.size(); i++){
      edge &e = edges[i];
      if( !uft.is_same(e.u, e.v) ){
	uft.unite(e.u, e.v);
	ret += e.cost;
      }
    }
    return ret;
  }
};

int N, W, H;
vector<tuple<long long, long long, int>> p;
vector<tuple<long long, long long, int>> q;
vector<tuple<long long, long long>> s;
int M[40000];
int main(){
  cin >> N >> W >> H;
  for(int i = 0; i < N; i++){
    long long x, y;
    cin >> x >> y;
    p.emplace_back(x,y,i);
    q.emplace_back(y,x,i);
    s.emplace_back(y,x);
  }
  UFT uft(N);
  SpanningTree st(N);
  sort(p.begin(), p.end());
  sort(q.begin(), q.end());

  for(int i = 1; i < N; i++){
    if( get<0>(p[i-1]) == get<0>(p[i]) ){
      uft.unite( get<2>(p[i-1]), get<2>(p[i]) );
      st.add_edge( get<2>(p[i-1]), get<2>(p[i]), 1 );
    }
    if( get<0>(q[i-1]) == get<0>(q[i]) ){
      uft.unite( get<2>(q[i-1]), get<2>(q[i]) );
      st.add_edge( get<2>(q[i-1]), get<2>(q[i]), 1 );
    }
  }
  long long sum_of_componentwise = st.kruskal();
  //cout << sum_of_componentwise << endl;
  int C = 0;
  for(int i = 0; i < N; i++){
    if( uft.parent[i] == i ){
      M[i] = C;
      C++;
    }
  }
  for(int i = 0; i < N; i++){
    if( uft.parent[i] != i ){
      M[i] = M[uft.find_root(i)];
    }
  }
  long long w0 = 0, wW = 0, h0 = 0, hH = 0;
  for(int i = 0; i < N; i++){
    long long x, y;
    tie(x,y) = s[i];
    if( x == 1 ){
      w0++;
    }else if( x == W ){
      wW++;
    }
    if( y == 1 ){
      h0++;
    }else if( y == H ){
      hH++;
    }
  }

  long long ans = 1e18;
  if( C == 1 ){
    ans = 0;
  }else{
    ans = min( ans, (C-w0) + C-1);
    ans = min( ans, (C-wW) + C-1);
    ans = min( ans, (C-h0) + C-1);
    ans = min( ans, (C-hH) + C-1);
  }
  cout << ans + sum_of_componentwise << endl;
}
accuracy: 0.07865168539325842
time_ms: 20
memory_kb: 7024
final_rank: 20

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

const int N = 40001;

int n, w, h;

int siz[N], par[N];

int findpar(int v) {
    if(v == par[v]) return v;
    else return par[v] = findpar(par[v]);
}

void combine(int a, int b) {
    a = findpar(a);
    b = findpar(b);

    if(a != b) {
        if(siz[a] < siz[b]) swap(a, b);
        par[b] = a;
        siz[a] += siz[b];
    }
}
 
int main(){
    ios_base::sync_with_stdio(false);   
    cin.tie(0);cout.tie(0); 

    cin >> n >> w >> h;

    vector<int> px[w + 1], py[h + 1];

    for(int i = 1; i <= n; i ++) {
        par[i] = i, siz[i] = 1;

        int x, y; cin >> x >> y;
        px[x].push_back(i);
        py[y].push_back(i);
    }

    for(int i = 1; i <= w; i ++) {
        for(int j = 1; j < px[i].size(); j ++) {
            combine(px[i][j - 1], px[i][j]);
        }
    }


    for(int i = 1; i <= h; i ++) {
        for(int j = 1; j < py[i].size(); j ++) {
            combine(py[i][j - 1], py[i][j]);
        }
    }

    int ans = 0, comp = 0;

    for(int i = 1; i <= n; i ++) {
        if(i == findpar(i)) {
            comp ++;
            ans += siz[i] - 1;
        }
    }


    if(comp == 1) {
        cout << ans << "\n";
        return 0;
    }

    if(px[1].size() || py[1].size() || px[w].size() || py[h].size()) cout << ans + (comp - 1) * 2<< "\n";
    else cout << ans + comp * 2 - 1 << "\n";    
}
accuracy: 1
time_ms: 10
memory_kb: 10444
final_rank: 8

code_snippet: #include <bits/stdc++.h>

using namespace std;

#define rep(i, a, b) for (int i = (int)(a); i < (int)(b); i++)
#define rrep(i, a, b) for (int i = (int)(b)-1; i >= (int)(a); i--)
#define ALL(v) (v).begin(), (v).end()
#define UNIQUE(v) sort(ALL(v)), (v).erase(unique(ALL(v)), (v).end())
#define SZ(v) (int)v.size()
#define MIN(v) *min_element(ALL(v))
#define MAX(v) *max_element(ALL(v))
#define LB(v, x) int(lower_bound(ALL(v), (x)) - (v).begin())
#define UB(v, x) int(upper_bound(ALL(v), (x)) - (v).begin())

using uint = unsigned int;
using ll = long long int;
using ull = unsigned long long;
using i128 = __int128_t;
using u128 = __uint128_t;
const int inf = 0x3fffffff;
const ll INF = 0x1fffffffffffffff;

template <typename T> inline bool chmax(T &a, T b) {
    if (a < b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T> inline bool chmin(T &a, T b) {
    if (a > b) {
        a = b;
        return 1;
    }
    return 0;
}
template <typename T, typename U> T ceil(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? (x + y - 1) / y : x / y);
}
template <typename T, typename U> T floor(T x, U y) {
    assert(y != 0);
    if (y < 0)
        x = -x, y = -y;
    return (x > 0 ? x / y : (x - y + 1) / y);
}
template <typename T> int popcnt(T x) {
    return __builtin_popcountll(x);
}
template <typename T> int topbit(T x) {
    return (x == 0 ? -1 : 63 - __builtin_clzll(x));
}
template <typename T> int lowbit(T x) {
    return (x == 0 ? -1 : __builtin_ctzll(x));
}

template <class T, class U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
    os << "P(" << p.first << ", " << p.second << ")";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec) {
    os << "{";
    for (int i = 0; i < vec.size(); i++) {
        os << vec[i] << (i + 1 == vec.size() ? "" : ", ");
    }
    os << "}";
    return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &map_var) {
    os << "{";
    for (auto itr = map_var.begin(); itr != map_var.end(); itr++) {
        os << "(" << itr->first << ", " << itr->second << ")";
        itr++;
        if (itr != map_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &set_var) {
    os << "{";
    for (auto itr = set_var.begin(); itr != set_var.end(); itr++) {
        os << *itr;
        ++itr;
        if (itr != set_var.end())
            os << ", ";
        itr--;
    }
    os << "}";
    return os;
}
#ifdef LOCAL
#define show(...) _show(0, #__VA_ARGS__, __VA_ARGS__)
#else
#define show(...) true
#endif
template <typename T> void _show(int i, T name) {
    cerr << '\n';
}
template <typename T1, typename T2, typename... T3>
void _show(int i, const T1 &a, const T2 &b, const T3 &...c) {
    for (; a[i] != ',' && a[i] != '\0'; i++)
        cerr << a[i];
    cerr << ":" << b << " ";
    _show(i + 1, a, c...);
}

/**
 * @brief template
 */

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

/**
 * @brief Union Find
 */

int main() {
    int N, M, K;
    cin >> K >> N >> M;
    UnionFind UF(K);
    vector<int> X(K), Y(K);
    vector<vector<int>> IDX(N), IDY(M);
    bool check = false;
    rep(i,0,K) {
        cin >> X[i] >> Y[i];
        X[i]--, Y[i]--;
        if (X[i] == 0 || X[i] == N-1 || Y[i] == 0 || Y[i] == M-1) check = true;
        IDX[X[i]].push_back(i);
        IDY[Y[i]].push_back(i);
    }
    int Comp = K;
    rep(i,0,N) {
        if (IDX[i].empty()) continue;
        rep(j,0,IDX[i].size()-1) if (UF.unite(IDX[i][j],IDX[i][j+1])) Comp--;
    }
    rep(i,0,M) {
        if (IDY[i].empty()) continue;
        rep(j,0,IDY[i].size()-1) if (UF.unite(IDY[i][j],IDY[i][j+1])) Comp--;
    }
    if (Comp == 1) cout << K-1 << endl;
    else if (check) cout << K-Comp+(Comp-1)*2 << endl;
    else cout << K-Comp+(Comp-1)*2+1 << endl;
}
accuracy: 1
time_ms: 10
memory_kb: 10232
final_rank: 6

code_snippet: // author: hanyu
#include <bits/stdc++.h>
using namespace std;

// Union-Find
struct UnionFind {
  vector<int> par; // 親のid(親の場合, 集合の頂点数の-1倍)
  
  UnionFind(int n): par(n, -1) {}
  
  int find(int x) {
    if (par[x] < 0) return x;
    return par[x] = find(par[x]);
  }
  
  bool unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y) return false;
    if (par[x] > par[y]) swap(x, y);
    par[x] += par[y];
    par[y] = x;
    return true;
  }
  
  bool same(int x, int y) {
    return find(x) == find(y);
  }
  
  int size(int x) {
    return -par[find(x)];
  }
};

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

  int n, w, h;
  cin >> n >> w >> h;

  bool edge = false;
  vector<vector<int>> hor(h), ver(w);
  for (int i = 0; i < n; i++) {
    int x, y;
    cin >> x >> y;
    x--;
    y--;
    hor[y].emplace_back(i);
    ver[x].emplace_back(i);
    if (x == 0 || y == 0 || x == w - 1 || y == h - 1) edge = true;
  }

  UnionFind uf(n);
  int ans = 0, sz = n;
  for (int i = 0; i < h; i++) {
    if (hor[i].size() >= 2) {
      for (int j = 1; j < hor[i].size(); j++) {
        if (uf.unite(hor[i][j], hor[i][j - 1])) {
          ans++;
          sz--;
        }
      }
    }
  }
  for (int i = 0; i < w; i++) {
    if (ver[i].size() >= 2) {
      for (int j = 1; j < ver[i].size(); j++) {
        if (uf.unite(ver[i][j], ver[i][j - 1])) {
          ans++;
          sz--;
        }
      }
    }
  }

  if (sz >= 2) {
    if (edge) ans += sz - 1 + sz - 1;
    else ans += sz + sz - 1;
  }

  cout << ans << endl;
}
accuracy: 1
time_ms: 10
memory_kb: 9904
final_rank: 3

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

class union_find {
  public:
    union_find(int n) : data(n, -1) {}
    int unite(int x, int y) {
        x = root(x), y = root(y);
        if(x != y) {
            if(size(x) < size(y)) swap(x, y);
            data[x] += data[y];
            return data[y] = x;
        }
        return -1;
    }
    int root(int x) { return data[x] < 0 ? x : data[x] = root(data[x]); }
    int size(int x) { return -data[root(x)]; }
    bool same(int x, int y) { return root(x) == root(y); }

  private:
    vector<int> data;
};

int main(){
    cin.tie(0);
    ios::sync_with_stdio(0);
    
    int N,W,H; cin >> N >> W >> H;
    union_find uf(N);
    vector<vector<int>> X(W), Y(H);
    int f = 0;
    rep(i,N) {
        int x,y; cin >> x >> y; x--, y--;
        X[x].push_back(i);
        Y[y].push_back(i);
        f |= x == 0 || x == W - 1 || y == 0 || y == H - 1;
    }
    
    for(auto &a : X) rep(i,int(a.size())-1) uf.unite(a[i], a[i + 1]);
    for(auto &a : Y) rep(i,int(a.size())-1) uf.unite(a[i], a[i + 1]);
    
    set<int> st;
    rep(i,N) st.insert(uf.root(i));
    if(st.size() == 1u) {
        cout << N - 1 << endl;
    } else {
        cout << N - 1 + int(st.size()) - f << endl;
    }
}
accuracy: 1
time_ms: 10
memory_kb: 11116
final_rank: 11

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

using ll = long long int;
using ull = unsigned long long int;
using P = pair<ll, ll>;
using P3 = pair<ll, P>;
using PP = pair<P, P>;
constexpr int INF32 = 1 << 30;
constexpr ll INF64 = 1LL << 62;
constexpr ll MOD = 1000000007;
// constexpr ll MOD = 998244353;
constexpr int di[] = {0, 1, 0, -1};
constexpr int dj[] = {1, 0, -1, 0};
constexpr int di8[] = {0, 1, 1, 1, 0, -1, -1, -1};
constexpr int dj8[] = {1, 1, 0, -1, -1, -1, 0, 1};
constexpr double EPS = 1e-10;
const double PI = acos(-1);

#define ALL(v) (v).begin(), (v).end()
#define REP(i, n) for (int i = 0, i_len = n; i < i_len; ++i)

template<typename T1,typename T2> bool chmax(T1 &a, T2 b) { if (a<b) { a=b; return 1; } return 0; }
template<typename T1,typename T2> bool chmin(T1 &a, T2 b) { if (b<a) { a=b; return 1; } return 0; }

template <int64_t Modulus>
struct Modint {
    using mint = Modint;
    long long v;
    Modint() : v(0) {}
    Modint(long long x) {
        x %= Modulus;
        if (x < 0) x += Modulus;
        v = x;
    }
    const long long& val() const { return v; }
    // 代入演算子
    mint& operator+=(const mint rhs) {
        v += rhs.v;
        if (v >= Modulus) v -= Modulus;
        return *this;
    }
    mint& operator-=(const mint rhs) {
        if (v < rhs.v) v += Modulus;
        v -= rhs.v;
        return *this;
    }
    mint& operator*=(const mint rhs) {
        v = v * rhs.v % Modulus;
        return *this;
    }
    mint& operator/=(mint rhs) { return *this = *this * rhs.inv(); }
    // 累乗, 逆元
    mint pow(long long n) const {
        mint x = *this, res = 1;
        while (n) {
            if (n & 1) res *= x;
            x *= x;
            n >>= 1;
        }
        return res;
    }
    mint inv() const { return pow(Modulus - 2); }
    // 算術演算子
    mint operator+(const mint rhs) const { return mint(*this) += rhs; }
    mint operator-(const mint rhs) const { return mint(*this) -= rhs; }
    mint operator*(const mint rhs) const { return mint(*this) *= rhs; }
    mint operator/(const mint rhs) const { return mint(*this) /= rhs; }
    mint operator-() const { return mint() - *this; }  // 単項
    // 入出力ストリーム
    friend ostream& operator<<(ostream& os, const mint& p) { return os << p.v; }
    friend istream& operator>>(istream& is, mint& p) {
        long long t;
        is >> t;
        p = mint(t);
        return (is);
    }
};

using mint = Modint<MOD>;

struct UnionFindTree {
    vector<int> par;
    vector<int> rank;
    vector<int> siz;
    vector<int> flag;
    int cnt = 0;

    void init(int n) {
        par.resize(n);
        rank.resize(n);
        siz.resize(n);
        flag.resize(n);
        cnt = n;
        for (int i = 0; i < n; i++) {
            par[i] = i;
            rank[i] = 0;
            siz[i] = 1;
        }
    }
    int find(int x) {
        if (par[x] == x) {
            return x;
        } else {
            return par[x] = find(par[x]);
        }
    }
    void unite(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) return;
        if (rank[x] < rank[y]) swap(x, y);
        if (rank[x] == rank[y]) rank[x]++;
        par[y] = x;
        siz[x] += siz[y];
        flag[x] |= flag[y];
        cnt--;
    }
    bool is_same(int x, int y) {
        return find(x) == find(y);
    }
    int size(int x) {
        x = find(x);
        return siz[x];
    }
};

int solve() {
    int n, w, h;
    cin >> n >> w >> h;
    UnionFindTree uf;
    uf.init(n);
    vector<vector<int>> xs(w), ys(h);
    for(int i=0;i<n;i++){
        int x, y;
        cin >> x >> y;
        x--; y--;
        xs[x].push_back(i);
        ys[y].push_back(i);
        if(x == 0)   uf.flag[i] |= 1;
        if(x == w-1) uf.flag[i] |= 1<<1;
        if(y == 0)   uf.flag[i] |= 1<<2;
        if(y == h-1) uf.flag[i] |= 1<<3;
    }
    for(int x=0;x<w;x++){
        for(int i=1;i<int(xs[x].size());i++){
            uf.unite(xs[x][0], xs[x][i]);
        }
    }
    for(int y=0;y<h;y++){
        for(int i=1;i<int(ys[y].size());i++){
            uf.unite(ys[y][0], ys[y][i]);
        }
    }
    int ans = n-1;
    if(uf.cnt > 1){
        int mi = n;
        vector<int> roots;
        vector<bool> used(n);
        for(int i=0;i<n;i++){
            int r = uf.find(i);
            if(!used[r]){
                roots.push_back(r);
                used[r] = true;
            }
        }
        for(int k=0;k<4;k++){
            int c = 0;
            for(auto i : roots){
                if((uf.flag[i]>>k&1)==0) c++;
            }
            chmin(mi, c);
        }
        ans += mi;
    }
    cout << ans << endl;
    return 0;
}

int main() {
    cin.tie(nullptr);
    ios::sync_with_stdio(false);
    solve();
    // while(!solve());
    // int t; cin >> t; for(int i=0;i<t;i++) solve();
    return 0;
}
accuracy: 1
time_ms: 10
memory_kb: 10700
final_rank: 9

code_snippet: #include<bits/stdc++.h>
using namespace std;
#include <unordered_set>
#include <random>
#define int long long
#define REP(i,m,n) for(int i=(m);i<(n);i++)
#define rep(i,n) REP(i,0,n)
#define pb push_back
#define all(a) a.begin(),a.end()
#define rall(c) (c).rbegin(),(c).rend()
#define mp make_pair
#define endl '\n'
//#define vec vector<ll>
//#define mat vector<vector<ll> >
#define fi first
#define se second
#define double long double
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
//typedef long double ld;
typedef complex<double> Complex;
const ll INF=1e9+7;
const ll MOD=INF;
const ll inf=INF*INF;
const ll mod=MOD;
const ll inv2=499122177;
const ll MAX=10000010;
const double PI=acos(-1.0);
typedef vector<vector<ll> > mat;
typedef vector<ll> vec;

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

//Union-Find木

struct UnionFind {
    vector< int > data;

    UnionFind() = default;

    explicit UnionFind(size_t sz) : data(sz, -1) {}

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

    int find(int k) {
        if(data[k] < 0) return (k);
        return data[k] = find(data[k]);
    }

    int size(int k) {
        return -data[find(k)];
    }

    bool same(int x, int y) {
        return find(x) == find(y);
    }
};

void solve(){
    ll N,W,H;cin>>N>>W>>H;
    swap(H,W);
    //同じ行か列のものをUnionFindして
    //連結成分数+N-1(1個の時だけN-1)
    UnionFind uf(N);
    vector<vector<ll> >a(H),b(W);
    ll z=0;
    rep(i,N){
        ll x,y;cin>>x>>y;
        x--;y--;
        if(x==0||y==0||x==H-1||y==W-1)z=1;
        a[x].pb(i);
        b[y].pb(i);
    }
    rep(i,H){
        REP(j,1,a[i].size())uf.unite(a[i][j],a[i][j-1]);
    }
    rep(i,W){
        REP(j,1,b[i].size())uf.unite(b[i][j],b[i][j-1]);
    }
    if(uf.size(0)==N){
        cout<<N-1<<endl;
        return;
    }
    vector<ll>used(N);
    ll cnt=0;
    rep(i,N){
        if(used[uf.find(i)])continue;
        used[uf.find(i)]=1;
        cnt++;
    }
    cout<<cnt+N-1-z<<endl;
}

signed main(){
    //cin.tie(0);
    //ios::sync_with_stdio(false);
    solve();
}
accuracy: 1
time_ms: 10
memory_kb: 10404
final_rank: 7

code_snippet: #include<bits/stdc++.h>
using namespace std;
#include <unordered_set>
#include <random>
#define int long long
#define REP(i,m,n) for(int i=(m);i<(n);i++)
#define rep(i,n) REP(i,0,n)
#define pb push_back
#define all(a) a.begin(),a.end()
#define rall(c) (c).rbegin(),(c).rend()
#define mp make_pair
#define endl '\n'
//#define vec vector<ll>
//#define mat vector<vector<ll> >
#define fi first
#define se second
#define double long double
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
//typedef long double ld;
typedef complex<double> Complex;
const ll INF=1e9+7;
const ll MOD=INF;
const ll inf=INF*INF;
const ll mod=MOD;
const ll inv2=499122177;
const ll MAX=10000010;
const double PI=acos(-1.0);
typedef vector<vector<ll> > mat;
typedef vector<ll> vec;

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

//Union-Find木

struct UnionFind {
    vector< int > data;

    UnionFind() = default;

    explicit UnionFind(size_t sz) : data(sz, -1) {}

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

    int find(int k) {
        if(data[k] < 0) return (k);
        return data[k] = find(data[k]);
    }

    int size(int k) {
        return -data[find(k)];
    }

    bool same(int x, int y) {
        return find(x) == find(y);
    }
};

void solve(){
    ll N,W,H;cin>>N>>W>>H;
    swap(H,W);
    //同じ行か列のものをUnionFindして
    //連結成分数+N-1(1個の時だけN-1)
    UnionFind uf(N);
    vector<vector<ll> >a(H),b(W);
    ll z=0;
    rep(i,N){
        ll x,y;cin>>x>>y;
        x--;y--;
        if(x==0||y==0||x==H-1||y==W)z=1;
        a[x].pb(i);
        b[y].pb(i);
    }
    rep(i,H){
        REP(j,1,a[i].size())uf.unite(a[i][j],a[i][j-1]);
    }
    rep(i,W){
        REP(j,1,b[i].size())uf.unite(b[i][j],b[i][j-1]);
    }
    if(uf.size(0)==N){
        cout<<N-1<<endl;
        return;
    }
    vector<ll>used(N);
    ll cnt=0;
    rep(i,N){
        if(used[uf.find(i)])continue;
        used[uf.find(i)]=1;
        cnt++;
    }
    cout<<cnt+N-1-z<<endl;
}

signed main(){
    //cin.tie(0);
    //ios::sync_with_stdio(false);
    solve();
}
accuracy: 0.25842696629213485
time_ms: 10
memory_kb: 10476
final_rank: 17

code_snippet: // I SELL YOU...! 
#include<iostream>
#include<vector>
#include<algorithm>
#include<functional>
#include<queue>
#include<chrono>
#include<iomanip>
#include<map>
#include<set>
using namespace std;
using ll = long long;
using P = pair<ll,ll>;
using TP = tuple<ll,ll,ll>;
void init_io(){
  cin.tie(0);
  ios::sync_with_stdio(false);
  cout << fixed << setprecision(18);
}
class union_find{
  int n;
  vector<int> par,rk,size;
  public:
  union_find(int max_n){
    n = max_n+1;
    for(int i=0;i<=n;i++){
      par.emplace_back(i);
      rk.emplace_back(0);
      size.emplace_back(1);
    }
  }
  int root(int x){
    vector<int> nodes;
    while(x!=par[x]){
      nodes.emplace_back(x);
      x = par[x];
    }
    for(auto v:nodes){
      par[v] = x;
    }
    return x;
  }
  void unite(int x,int y){
    x = root(x);
    y = root(y);
    if(x==y) return;
    if(rk[x]<rk[y]){
      par[x] = y;
      size[y] += size[x];
    }else{
      par[y] = x;
      if(rk[x]==rk[y]) rk[x]++;
      size[x] += size[y];
    }
  }
  bool same(int x,int y){
    return root(x)==root(y);
  }
  int treesize(int x){
    return size[root(x)];
  }
};
signed main(){
  init_io();
  ll h,w,n;
  bool kabe = false;
  cin >> n >> w >> h;
  union_find uf(n);
  vector<ll> x(n),y(n);
  map<ll, vector<ll>> mpx, mpy;
  for (int i=0;i<n;i++) {
    cin >> x[i] >> y[i];
    mpx[x[i]].push_back(i);
    mpy[y[i]].push_back(i);
    if (x[i] == 1 || y[i] == 1 || x[i] == w || y[i] == h) kabe = true;
  }
  for (auto [_, vx]: mpx) {
    for (auto idx: vx) {
      uf.unite(idx, vx.front());
    }
  }
  for (auto [_, vy]: mpy) {
    for (auto idx: vy) {
      uf.unite(idx, vy.front());
    }
  }
  vector<ll> used(n, 0);
  ll s = 0;
  ll ans = 0;
  for (int i=0;i<n;i++) {
    ll p = uf.root(i);
    if (!used[p]) {
      used[p] = true;
      s++;
      ans += uf.treesize(p)-1;
    }
  }
  if (s>1) {
    if (kabe) ans += 2 * (s-1);
    else ans += 2*s-1;
  }
  cout << ans << endl;
}
accuracy: 1
time_ms: 20
memory_kb: 11688
final_rank: 12

code_snippet: #include<bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;

//only for atcoder
//#include<atcoder/all>
//using namespace atcoder;

#define rep(i,l,r) for(ll i=(l); i<(r); i++)
#define rrep(i,l,r) for(ll i=(r)-1; i>=(l); i--)
#define ALL(c) (c).begin(), (c).end()
#define RALL(c) (c).rbegin(), (c).rend()
#define SORT(c) sort(ALL(c))
#define RSORT(c) sort(RALL(c))
#define MINV(c) *min_element(ALL(c))
#define MAXV(c) *max_element(ALL(c))

using VI = vector<int>;
using VVI = vector<VI>;
using VL = vector<ll>;
using VVL = vector<VL>;
using VS = vector<string>;
using VVS = vector<VS>;
using VB = vector<bool>;
using VVB = vector<VB>;
using VC = vector<char>;
using VVC = vector<VC>;
using VD = vector<ld>;
using VVD = vector<VD>;
using P = pair<ll,ll>;
using VP = vector<P>;
const ll LINF = 2e18;
const int INF = 2e9;

//構造体ver
struct UnionFind{
  vector<int> parent;
  vector<int> group_size;
  int number_groups;
  
  void init(int N){
    parent.resize(N);
    group_size.resize(N);
    for(int i=0; i<N; i++){
      parent[i] = i;
      group_size[i] = 1;
    }
    number_groups = N;
  }
  
  int root(int x){
    while(parent[x] != x){
      x = parent[x];
    }
    return x;
  }
  
  bool same(int x, int y){
    return root(x) == root(y);
  }
  
  void merge(int x, int y){
    x = root(x);
    y = root(y);
    if(x != y){
      number_groups--;
      if(group_size[x] < group_size[y]){
        swap(x,y);
      }
      group_size[x] += group_size[y];
      parent[y] = x;
    }
  }
  
  int SIZE(int x){
    return group_size[root(x)];
  }
  
  int X(){
    return number_groups;
  }
  
};


void solve(){
  int N, H, W;
  cin >> N >> H >> W;
  VP vec(N);
  rep(i,0,N){
    int x, y;
    cin >> x >> y;
    x--; y--;
    vec[i] = P(x,y);
  }
  VVI h(H,VI(0));
  VVI w(W,VI(0));
  rep(i,0,N){
    auto[x,y] = vec[i];
    h[x].push_back(i);
    w[y].push_back(i);
  }
  
  UnionFind D;
  D.init(N);
  
  rep(i,0,H){
    if(h[i].size() <= 1){
      continue;
    }
    rep(j,0,h[i].size()-1){
      D.merge(h[i][j], h[i][j+1]);
    }
  }
  rep(i,0,W){
    if(w[i].size() <= 1){
      continue;
    }
    rep(j,0,w[i].size()-1){
      D.merge(w[i][j], w[i][j+1]);
    }
  }
  
  vector<set<int>> S(4);
  int cnt = 0;
  //0 x=0,  1 x=H-1, 2 y=0, 3 y=W-1
  rep(i,0,N){
    auto[x,y] = vec[i];
    if(x == 0){
      S[0].insert(D.root(i));
    }
    if(x == H-1){
      S[1].insert(D.root(i));
    }
    if(y == 0){
      S[2].insert(D.root(i));
    }
    if(y == W-1){
      S[3].insert(D.root(i));
    }
    if(i == D.root(i)){
      cnt++;
    }
  }
  
  int ans = N-1;
  
  if(cnt == 1){
    cout << ans << endl;
    return;
  }
  
  int need = 0;
  rep(i,0,4){
    need = max(need, (int)S[i].size());
  }
  
  cout << ans + cnt - need << endl;
  
}

int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int T;
  //cin >> T;
  T = 1;
  for(int i=0; i<T; i++){
    solve();
  }
  
  /*while(true){
    int N, M;
    cin >> N >> M;
    if(N == 0){
      return 0;
    }
    solve(N,M);
  }*/
}
accuracy: 1
time_ms: 10
memory_kb: 10764
final_rank: 10

code_snippet: #include <bits/stdc++.h>
#pragma GCC optimize("Ofast")
#define _GLIBCXX_DEBUG
using namespace std;
using std::cout;
using std::cin;
using std::endl;
using ll=long long;
using ld=long double;
ll ILL=1167167167167167167;
const int INF=2100000000;
const ll mod=998244353;
#define rep(i,a) for (ll i=0;i<a;i++)
#define all(p) p.begin(),p.end()
template<class T> using _pq = priority_queue<T, vector<T>, greater<T>>;
template<class T> ll LB(vector<T> &v,T a){return lower_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> ll UB(vector<T> &v,T a){return upper_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> bool chmin(T &a,const T &b){if(a>b){a=b;return 1;}else return 0;}
template<class T> bool chmax(T &a,const T &b){if(a<b){a=b;return 1;}else return 0;}
template<class T> void So(vector<T> &v) {sort(v.begin(),v.end());}
template<class T> void Sore(vector<T> &v) {sort(v.begin(),v.end(),[](T x,T y){return x>y;});}
void yneos(bool a){if(a) cout<<"Yes\n"; else cout<<"No\n";}
template<class T> void vec_out(vector<T> &p){for(int i=0;i<(int)(p.size());i++){if(i) cout<<" ";cout<<p[i];}cout<<"\n";}

namespace po167{
struct UFtree
{
	std::vector<int> wei;
	std::vector<int> q;
	int component;
	UFtree(int n):wei(n),component(n),par(n){
		for(int i=0;i<n;i++){
			wei[i]=1,par[i]=i;
		}
	}
	void intialize(){
		for(auto x:q){
			wei[root(x)]=1;
			par[x]=x;
		}
		component=(int)par.size();
		q={};
	}
	//根っこを返す
	int root(int a){
		if(a==par[a]) return a;
		return par[a]=root(par[a]);
	}
	//trueなら1,falseなら0
	int same(int a,int b){
		if(root(a)==root(b)) return 1;
		else return 0;
	}
	//a,bが違う根っこの元なら結合する,結合したらtrueを返す
	bool unite(int a,int b){
		a=root(a),b=root(b);
		if(a==b) return false;
		if(wei[a]<wei[b]) std::swap(a,b);
		par[b]=a;
		q.push_back(b);
		wei[a]+=wei[b];
		component--;
		return true;
	}
	private:
	std::vector<int> par;
};
}
using po167::UFtree;


void solve();
// oddloop
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	int t=1;
	//cin>>t;
	rep(i,t) solve();
}

void solve(){
	int N,H,W;
	cin>>N>>H>>W;
	vector<vector<int>> G(H+W);
	int J=1;
	rep(i,N){
		int a,b; 
		cin>>a>>b; 
		a--,b--;
		G[a].push_back(i);
		G[b+H].push_back(i);
		if(min(a,b)==0) J=0;
		if(a==H-1||b==W-1) J=0;
	}
	UFtree T(N);
	rep(i,H+W){
		rep(j,(int)(G[i].size())-1){
			T.unite(G[i][j],G[i][j+1]);
		}
	}
	int ans=N-T.component;
	if(T.component==1){
		cout<<ans<<"\n";
	}else{
		cout<<ans+T.component*2-2+J<<"\n";
	}
}
accuracy: 1
time_ms: 10
memory_kb: 10228
final_rank: 5

code_snippet: #include <bits/stdc++.h>
#pragma GCC optimize("Ofast")
#define _GLIBCXX_DEBUG
using namespace std;
using std::cout;
using std::cin;
using std::endl;
using ll=long long;
using ld=long double;
ll ILL=1167167167167167167;
const int INF=2100000000;
const ll mod=998244353;
#define rep(i,a) for (ll i=0;i<a;i++)
#define all(p) p.begin(),p.end()
template<class T> using _pq = priority_queue<T, vector<T>, greater<T>>;
template<class T> ll LB(vector<T> &v,T a){return lower_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> ll UB(vector<T> &v,T a){return upper_bound(v.begin(),v.end(),a)-v.begin();}
template<class T> bool chmin(T &a,const T &b){if(a>b){a=b;return 1;}else return 0;}
template<class T> bool chmax(T &a,const T &b){if(a<b){a=b;return 1;}else return 0;}
template<class T> void So(vector<T> &v) {sort(v.begin(),v.end());}
template<class T> void Sore(vector<T> &v) {sort(v.begin(),v.end(),[](T x,T y){return x>y;});}
void yneos(bool a){if(a) cout<<"Yes\n"; else cout<<"No\n";}
template<class T> void vec_out(vector<T> &p){for(int i=0;i<(int)(p.size());i++){if(i) cout<<" ";cout<<p[i];}cout<<"\n";}

namespace po167{
struct UFtree
{
	std::vector<int> wei;
	std::vector<int> q;
	int component;
	UFtree(int n):wei(n),component(n),par(n){
		for(int i=0;i<n;i++){
			wei[i]=1,par[i]=i;
		}
	}
	void intialize(){
		for(auto x:q){
			wei[root(x)]=1;
			par[x]=x;
		}
		component=(int)par.size();
		q={};
	}
	//根っこを返す
	int root(int a){
		if(a==par[a]) return a;
		return par[a]=root(par[a]);
	}
	//trueなら1,falseなら0
	int same(int a,int b){
		if(root(a)==root(b)) return 1;
		else return 0;
	}
	//a,bが違う根っこの元なら結合する,結合したらtrueを返す
	bool unite(int a,int b){
		a=root(a),b=root(b);
		if(a==b) return false;
		if(wei[a]<wei[b]) std::swap(a,b);
		par[b]=a;
		q.push_back(b);
		wei[a]+=wei[b];
		component--;
		return true;
	}
	private:
	std::vector<int> par;
};
}
using po167::UFtree;


void solve();
// oddloop
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	int t=1;
	//cin>>t;
	rep(i,t) solve();
}

void solve(){
	int N,H,W;
	cin>>N>>H>>W;
	vector<vector<int>> G(H+W);
	int J=1;
	rep(i,N){
		int a,b; 
		cin>>a>>b; 
		a--,b--;
		G[a].push_back(i);
		G[b+W].push_back(i);
		if(min(a,b)==0) J=0;
		if(a==W-1||b==H-1) J=0;
	}
	UFtree T(N);
	rep(i,H+W){
		rep(j,(int)(G[i].size())-1){
			T.unite(G[i][j],G[i][j+1]);
		}
	}
	int ans=N-T.component;
	if(T.component==1){
		cout<<ans<<"\n";
	}else{
		cout<<ans+T.component*2-2+J<<"\n";
	}
}
accuracy: 0.10112359550561797
time_ms: 10
memory_kb: 10284
final_rank: 19

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

struct UnionFind {
    vector<int> parents;

    UnionFind() {}
    UnionFind(int n) : parents(n, -1) {}

    int leader(int x) {
        if (parents[x] < 0)
            return x;
        else
            return parents[x] = leader(parents[x]);
    }
    bool merge(int x, int y) {
        x = leader(x);
        y = leader(y);
        if (x == y) return false;
        if (parents[x] > parents[y]) swap(x, y);
        parents[x] += parents[y];
        parents[y] = x;
        return true;
    }
    bool same(int x, int y) { return leader(x) == leader(y); }
    int size(int x) { return -parents[leader(x)]; }
    void init(int n) { parents.assign(n, -1); }  // reset
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int N, H, W;
    cin >> N >> H >> W;
    vector<int> x(N), y(N);
    rep(i, N) cin >> x[i] >> y[i];
    UnionFind uf(N);
    rep(j, N) {
        rep(i, j) {
            if (x[i] == x[j] || y[i] == y[j]) {
                uf.merge(i, j);
            }
        }
    }
    vector<int> x0(N, 0), xH(N, 0), y0(N, 0), yW(N, 0);
    rep(i, N) {
        if (x[i] == 1) x0[uf.leader(i)] = 1;
        if (x[i] == H) xH[uf.leader(i)] = 1;
        if (y[i] == 1) y0[uf.leader(i)] = 1;
        if (y[i] == W) yW[uf.leader(i)] = 1;
    }
    int csize = 0;
    rep(i, N) {
        if (i == uf.leader(i)) csize++;
    }
    int ans = N - csize;
    if (csize == 1) {
        cout << ans << '\n';
        return 0;
    }
    int sx0 = 0, sxH = 0, sy0 = 0, syW = 0;
    rep(i, N) {
        sx0 += x0[i];
        sxH += xH[i];
        sy0 += y0[i];
        syW += yW[i];
    }
    ans += csize - 1;
    ans += csize - max({sx0, sxH, sy0, syW});
    cout << ans << '\n';
    return 0;
}
accuracy: 1
time_ms: 850
memory_kb: 4100
final_rank: 15

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

struct UnionFind {
    vector<int> parents;

    UnionFind() {}
    UnionFind(int n) : parents(n, -1) {}

    int leader(int x) {
        if (parents[x] < 0)
            return x;
        else
            return parents[x] = leader(parents[x]);
    }
    bool merge(int x, int y) {
        x = leader(x);
        y = leader(y);
        if (x == y) return false;
        if (parents[x] > parents[y]) swap(x, y);
        parents[x] += parents[y];
        parents[y] = x;
        return true;
    }
    bool same(int x, int y) { return leader(x) == leader(y); }
    int size(int x) { return -parents[leader(x)]; }
    void init(int n) { parents.assign(n, -1); }  // reset
};
// clang-format off
template<class T, class U> ostream &operator<<(ostream &os, const pair<T, U> &p) { return os << "(" << p.first << ", " << p.second << ")"; }
template<class T> ostream &operator<<(ostream &os, const vector<T> &v) { os << "[ "; for (auto &z : v) os << z << " "; os << "]"; return os; }
template<class T> ostream &operator<<(ostream &os, const set<T> &v) { os << "{ "; for (auto &z : v) os << z << " "; os << "}"; return os; }
template<class T> ostream &operator<<(ostream &os, const multiset<T> &v) { os << "{ "; for (auto &z : v) os << z << " "; os << "}"; return os; }
#ifdef _DEBUG
#define show(x) cout << #x << " = " << x << endl;
#else
#define show(x)
#endif
// clang-format on

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int N, H, W;
    cin >> N >> H >> W;
    vector<int> x(N), y(N);
    rep(i, N) cin >> x[i] >> y[i];
    UnionFind uf(N);
    rep(j, N) {
        rep(i, j) {
            if (x[i] == x[j] || y[i] == y[j]) {
                uf.merge(i, j);
            }
        }
    }
    vector<int> x0(N, 0), xH(N, 0), y0(N, 0), yW(N, 0);
    rep(i, N) {
        if (x[i] == 1) x0[uf.leader(i)] = 1;
        if (x[i] == H) xH[uf.leader(i)] = 1;
        if (y[i] == 1) y0[uf.leader(i)] = 1;
        if (y[i] == W) yW[uf.leader(i)] = 1;
    }
    int csize = 0;
    rep(i, N) {
        if (i == uf.leader(i)) csize++;
    }
    int ans = N - csize;
    if (csize == 1) {
        cout << ans << '\n';
        return 0;
    }
    show(ans);
    int sx0 = 0, sxH = 0, sy0 = 0, syW = 0;
    rep(i, N) {
        sx0 += x0[i];
        sxH += xH[i];
        sy0 += y0[i];
        syW += yW[i];
    }
    ans += csize - 1;
    show(ans);
    ans += csize - max({sx0, sxH, sy0, syW});
    show(ans);
    cout << ans << '\n';
    return 0;
}
accuracy: 1
time_ms: 850
memory_kb: 4096
final_rank: 13

code_snippet: /*	author:  Kite_kuma
	created: 2021.10.27 19:05:52 */

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

#pragma region macros

#define foa(s, v) for(auto &s : v)
#define all(v) (v).begin(), (v).end()
#define rall(v) (v).rbegin(), (v).rend()
#define popcnt(n) __builtin_popcountll((long long)n)

#define REPname(a, b, c, d, e, ...) e
#define rep(...) REPname(__VA_ARGS__, REP3, REP2, REP1, REP0)(__VA_ARGS__)
#define REP0(x) for(int Counter_in_rep_macro = 0; Counter_in_rep_macro < (x); ++Counter_in_rep_macro)
#define REP1(i, x) for(int i = 0; i < (x); ++i)
#define REP2(i, l, r) for(int i = (l); i < (r); ++i)
#define REP3(i, l, r, c) for(int i = (l); i < (r); i += (c))

#define DREPname(a, b, c, d, e, ...) e
#define drep(...) DREPname(__VA_ARGS__, DREP3, DREP2, DREP1)(__VA_ARGS__)
#define DREP1(i, x) for(int i = (x)-1; i >= 0; --i)
#define DREP2(i, l, r) for(int i = (r)-1; i >= (l); --i)
#define DREP3(i, l, r, c) for(int i = (r)-1; i >= (l); i -= (c))

#pragma endregion

#pragma region aliases

using ll = long long;
using ld = long double;
using ull = unsigned long long;
using vi = std::vector<int>;
using vvi = std::vector<std::vector<int>>;
using vvvi = std::vector<std::vector<std::vector<int>>>;
using vll = std::vector<ll>;
using vvll = std::vector<vll>;
using vvvll = std::vector<vvll>;
using pii = std::pair<int, int>;
using pll = std::pair<long long, long long>;
template <class T = ll>
using V = std::vector<T>;
template <class T = ll>
using VV = V<V<T>>;
template <class T = ll>
using VVV = V<V<V<T>>>;
template <class T = ll>
using pqup = std::priority_queue<T, std::vector<T>, std::greater<T>>;
template <class T = ll>
using pqdn = std::priority_queue<T>;

#pragma endregion

#pragma region constants

const int inf = 1e9;
const long long INF = 1e18;
const long double pi = acos(-1);
const char dl = '\n';
const char sp = ' ';
int dx[8] = {1, 0, -1, 0, 1, -1, -1, 1};
int dy[8] = {0, 1, 0, -1, 1, 1, -1, -1};

const int mod_1000000007 = 1000000007;
const int mod_998244353 = 998244353;

#pragma endregion

#pragma region basic_operation

template <class T0, class T1, class T2>
inline bool in_range(T0 x, T1 lef, T2 rig) {
	return ((lef <= x) && (x < rig));
}

template <class T>
inline bool chmin(T &a, T b) {
	if(a > b) {
		a = b;
		return true;
	}
	return false;
}
template <class T>
inline bool chmax(T &a, T b) {
	if(a < b) {
		a = b;
		return true;
	}
	return false;
}

void Yes(bool f = 1) { std::cout << (f ? "Yes" : "No") << '\n'; }
void No() { std::cout << "No\n"; }
void YES(bool f = 1) { std::cout << (f ? "YES" : "NO") << '\n'; }
void NO() { std::cout << "NO\n"; }

template <class T>
void drop(T answer) {
	std::cout << answer << '\n';
	exit(0);
}

void err(bool flag = true) {
	if(!flag) return;
	std::cout << -1 << '\n';
	exit(0);
}

template <class T>
void vout(std::vector<T> const &v, bool vertically = 0) {
	if(vertically) {
		for(auto const &a : v) {
			std::cout << a << '\n';
		}
		return;
	}
	for(auto it = v.begin(); it != v.end(); it++) {
		std::cout << (*it);
		if(it != v.end() - 1) {
			std::cout << ' ';
		}
	}
	std::cout << '\n';
	return;
}

inline void print() { std::cout << '\n'; }
template <class T>
inline void print(T x) {
	std::cout << x << '\n';
	return;
}
template <typename Head, typename... Tail>
void print(Head H, Tail... T) {
	std::cout << H << " ";
	print(T...);
}

template <class T>
void add(std::vector<T> &v, T val) {
	for(auto &a : v) a += val;
	return;
}

template <class T>
T dup(T a, T b) {
	assert(b != 0);
	return (a + b - 1) / b;
}

template <class T>
T greatest_lower_multiple(T x, T d) {
	if(d == 0) return 0;
	if(d < 0) d *= -1;
	T y = x % d;
	if(y < 0) y += d;
	return x - y;
}

template <class T>
T least_upper_multiple(T x, T d) {
	return -greatest_lower_multiple(-x, d);
}

long long POW(long long a, long long n) {
	long long res = 1;
	while(n > 0) {
		if(n & 1) res = res * a;
		a = a * a;
		n >>= 1;
	}
	return res;
}

long long modpow(long long a, long long n, long long mod) {	 // a^n mod
	assert(n >= 0 && mod);
	if(mod == 1) return 0LL;
	long long res = 1;
	a %= mod;
	while(n > 0) {
		if(n & 1) res = res * a % mod;
		a = a * a % mod;
		n >>= 1;
	}
	return res < 0 ? res + mod : res;
}

// return x which satisfies a * x % mod == gcd(a, mod)
// not (mod | a)
long long modinv(long long a, long long mod) {
	long long b = mod, u = 1, v = 0;
	while(b) {
		long long t = a / b;
		a -= t * b;
		std::swap(a, b);
		u -= t * v;
		std::swap(u, v);
	}
	u %= mod;
	if(u < 0) u += mod;
	return u;
}

template <class T>
int lb(const std::vector<T> &a, const T x) {
	return std::distance(a.begin(), std::lower_bound(a.begin(), a.end(), x));
}
template <class T>
int ub(const std::vector<T> &a, const T x) {
	return std::distance(a.begin(), std::upper_bound(a.begin(), a.end(), x));
}
template <class T>
void unq_sort(std::vector<T> &a) {
	std::sort(a.begin(), a.end());
	a.erase(std::unique(a.begin(), a.end()), a.end());
}
template <class T>
std::vector<int> press(std::vector<T> &a) {
	auto vec = a;
	unq_sort(vec);
	std::vector<int> ret;
	for(auto &v : a) ret.push_back(lb(vec, v));
	return ret;
}

#pragma endregion

#pragma region input
#define VEC(type, name, size) \
	vector<type> name(size);  \
	scanner::INPUT(name)
#define VVEC(type, name, h, w)                     \
	vector<vector<type>> name(h, vector<type>(w)); \
	scanner::INPUT(name)
#define INT(...)     \
	int __VA_ARGS__; \
	scanner::INPUT(__VA_ARGS__)
#define LL(...)            \
	long long __VA_ARGS__; \
	scanner::INPUT(__VA_ARGS__)
#define STR(...)        \
	string __VA_ARGS__; \
	scanner::INPUT(__VA_ARGS__)
#define CHR(...)      \
	char __VA_ARGS__; \
	scanner::INPUT(__VA_ARGS__)
#define DBL(...)        \
	double __VA_ARGS__; \
	scanner::INPUT(__VA_ARGS__)
#define LD(...)              \
	long double __VA_ARGS__; \
	scanner::INPUT(__VA_ARGS__)
#define TPL3(type0, type1, type2, name)   \
	std::tuple<type0, type1, type2> name; \
	scanner::INPUT(name);
#define TPL4(type0, type1, type2, type3, name)   \
	std::tuple<type0, type1, type2, type3> name; \
	scanner::INPUT(name);

namespace scanner {
template <class T>
void scan(T &a) {
	std::cin >> a;
}

template <class T, class L>
void scan(std::pair<T, L> &p) {
	scan(p.first);
	scan(p.second);
}

template <class T0, class T1, class T2>
void scan(std::tuple<T0, T1, T2> &p) {
	T0 t0;
	T1 t1;
	T2 t2;
	scan(t0);
	scan(t1);
	scan(t2);
	p = std::make_tuple(t0, t1, t2);
}

template <class T0, class T1, class T2, class T3>
void scan(std::tuple<T0, T1, T2, T3> &p) {
	T0 t0;
	T1 t1;
	T2 t2;
	T3 t3;
	scan(t0);
	scan(t1);
	scan(t2);
	scan(t3);
	p = std::make_tuple(t0, t1, t2, t3);
}

template <class T>
void scan(std::vector<T> &a) {
	for(auto &i : a) scan(i);
}

void INPUT() {}
template <class Head, class... Tail>
void INPUT(Head &head, Tail &... tail) {
	scan(head);
	INPUT(tail...);
}
}  // namespace scanner

template <typename T1, typename T2>
std::istream &operator>>(std::istream &is, std::pair<T1, T2> &p) {
	is >> p.first >> p.second;
	return is;
}
#pragma endregion

#pragma region debug

#pragma region output
template <typename T1, typename T2>
std::ostream &std::operator<<(std::ostream &os, const std::pair<T1, T2> &p) {
	os << p.first << " " << p.second;
	return os;
}
template <class T>
std::ostream &std::operator<<(std::ostream &os, const std::vector<T> &v) {
	for(int i = 0; i < (int)v.size(); i++) {
		if(i) os << " ";
		os << v[i];
	}
	return os;
}
#pragma endregion

#pragma region view

namespace viewer {
void view(const long long e) {
	if(e == INF)
		std::cerr << "INF";
	else if(e == -INF)
		std::cerr << "-INF";
	else
		std::cerr << e;
}

void view(const int e) {
	if(e == inf)
		std::cerr << "inf";
	else if(e == -inf)
		std::cerr << "-inf";
	else
		std::cerr << e;
}

template <typename T>
void view(const T e) {
	std::cerr << e;
}

template <typename T, typename U>
void view(const std::pair<T, U> &p) {
	std::cerr << "(";
	view(p.first);
	std::cerr << ", ";
	view(p.second);
	std::cerr << ")";
}

template <class T0, class T1, class T2>
void view(const std::tuple<T0, T1, T2> &p) {
	std::cerr << "(";
	view(std::get<0>(p));
	std::cerr << ", ";
	view(std::get<1>(p));
	std::cerr << ", ";
	view(std::get<2>(p));
	std::cerr << ")";
}

template <class T0, class T1, class T2, class T3>
void view(const std::tuple<T0, T1, T2, T3> &p) {
	std::cerr << "(";
	view(std::get<0>(p));
	std::cerr << ", ";
	view(std::get<1>(p));
	std::cerr << ", ";
	view(std::get<2>(p));
	std::cerr << ", ";
	view(std::get<3>(p));
	std::cerr << ")";
}

template <typename T>
void view(const std::set<T> &s) {
	if(s.empty()) {
		std::cerr << "{ }";
		return;
	}
	std::cerr << "{ ";
	for(auto &t : s) {
		view(t);
		std::cerr << ", ";
	}
	std::cerr << "\b\b }";
}

template <typename T>
void view(const std::unordered_set<T> &s) {
	if(s.empty()) {
		std::cerr << "{ }";
		return;
	}
	std::cerr << "{ ";
	for(auto &t : s) {
		view(t);
		std::cerr << ", ";
	}
	std::cerr << "\b\b }";
}

template <typename T>
void view(const std::vector<T> &v) {
	if(v.empty()) {
		std::cerr << "{ }";
		return;
	}
	std::cerr << "{ ";
	for(const auto &e : v) {
		view(e);
		std::cerr << ", ";
	}
	std::cerr << "\b\b }";
}

template <typename T>
void view(const std::vector<std::vector<T>> &vv) {
	std::cerr << "{\n";
	for(const auto &v : vv) {
		std::cerr << "\t";
		view(v);
		std::cerr << '\n';
	}
	std::cerr << "}";
}

template <typename T, typename U>
void view(const std::vector<std::pair<T, U>> &v) {
	std::cerr << "{\n";
	for(const auto &c : v) {
		std::cerr << "\t(";
		view(c.first);
		std::cerr << ", ";
		view(c.second);
		std::cerr << ")\n";
	}
	std::cerr << "}";
}

template <class T0, class T1, class T2>
void view(const std::vector<std::tuple<T0, T1, T2>> &v) {
	if(v.empty()) {
		std::cerr << "{ }";
		return;
	}
	std::cerr << '{';
	for(const auto &t : v) {
		std::cerr << "\n\t";
		view(t);
		std::cerr << ",";
	}
	std::cerr << "\n}";
}

template <class T0, class T1, class T2, class T3>
void view(const std::vector<std::tuple<T0, T1, T2, T3>> &v) {
	if(v.empty()) {
		std::cerr << "{ }";
		return;
	}
	std::cerr << '{';
	for(const auto &t : v) {
		std::cerr << "\n\t";
		view(t);
		std::cerr << ",";
	}
	std::cerr << "\n}";
}

template <typename T, typename U>
void view(const std::map<T, U> &m) {
	std::cerr << "{\n";
	for(const auto &t : m) {
		std::cerr << "\t[";
		view(t.first);
		std::cerr << "] : ";
		view(t.second);
		std::cerr << '\n';
	}
	std::cerr << "}";
}

template <typename T, typename U>
void view(const std::unordered_map<T, U> &m) {
	std::cerr << "{\n";
	for(const auto &t : m) {
		std::cerr << "\t[";
		view(t.first);
		std::cerr << "] : ";
		view(t.second);
		std::cerr << '\n';
	}
	std::cerr << "}";
}
}  // namespace viewer

#pragma endregion

// when debugging : g++ foo.cpp -DLOCAL
#ifdef LOCAL
void debug_out() {}
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
	viewer::view(H);
	std::cerr << ", ";
	debug_out(T...);
}
#define debug(...)                                                \
	do {                                                          \
		std::cerr << __LINE__ << " [" << #__VA_ARGS__ << "] : ["; \
		debug_out(__VA_ARGS__);                                   \
		std::cerr << "\b\b]\n";                                   \
	} while(0)
#define dump(x)                                      \
	do {                                             \
		std::cerr << __LINE__ << " " << #x << " : "; \
		viewer::view(x);                             \
		std::cerr << '\n';                           \
	} while(0)

#else
#define debug(...) (void(0))
#define dump(x) (void(0))
#endif

#pragma endregion

#pragma region union_find

struct UF {
   private:
	std::vector<int> data;	// data[root] = -size, data[not_root] = parent

   public:
	int components;	 // number of components

	UF(int N) : data(N, -1), components(N) {}

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

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

	bool unite(int x, int y) {	// merge x and y
		x = root(x);
		y = root(y);
		if(x == y) return false;
		components--;
		if(data[x] > data[y]) std::swap(x, y);	// balancing
		data[x] += data[y];
		data[y] = x;
		return true;
	}

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

	std::vector<std::vector<int>> groups() {
		std::vector<std::vector<int>> g(data.size()), ret;
		for(size_t i = 0; i < data.size(); i++) g[root(i)].push_back((int)i);
		for(auto &vec : g)
			if(!vec.empty()) ret.push_back(vec);
		return ret;
	}
};

#pragma endregion

int solve(int n) {
	int h, w;
	cin >> h >> w;
	V<pair<int, int>> r(n);
	foa(t, r) cin >> t;
	sort(all(r));

	map<int, int> col_idx;

	int hashi = 0;

	UF uf(n);
	rep(i, n) {
		int x, y;
		tie(x, y) = r[i];

		if(x == 1 || h == x || y == 1 || y == w) hashi = 1;

		if(i && x == r[i - 1].first) {
			uf.unite(i, i - 1);
		}
		if(col_idx.count(y)) {
			uf.unite(col_idx[y], i);
		}
		col_idx[y] = i;
	}

	debug(r);
	// debug(uf);

	debug(uf.groups());

	int ans = n - 1;
	if(uf.groups().size() > 1){
		ans += 1 - hashi;
		ans += uf.groups().size() - 1;
	}

	return ans;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout << std::fixed << std::setprecision(15);
	srand((unsigned)time(NULL));

	int n;
	while(cin >> n) cout << solve(n) << dl;

	return 0;
}
accuracy: 1
time_ms: 20
memory_kb: 8316
final_rank: 1

code_snippet: // #pragma comment(linker, "/stack:200000000")

#include <bits/stdc++.h>

#include <limits>
#include <type_traits>

namespace suisen {
// ! utility
template <typename ...Types>
using constraints_t = std::enable_if_t<std::conjunction_v<Types...>, std::nullptr_t>;
template <bool cond_v, typename Then, typename OrElse>
constexpr decltype(auto) constexpr_if(Then&& then, OrElse&& or_else) {
    if constexpr (cond_v) {
        return std::forward<Then>(then);
    } else {
        return std::forward<OrElse>(or_else);
    }
}

// ! function
template <typename ReturnType, typename Callable, typename ...Args>
using is_same_as_invoke_result = std::is_same<std::invoke_result_t<Callable, Args...>, ReturnType>;
template <typename F, typename T>
using is_uni_op = is_same_as_invoke_result<T, F, T>;
template <typename F, typename T>
using is_bin_op = is_same_as_invoke_result<T, F, T, T>;

template <typename Comparator, typename T>
using is_comparator = std::is_same<std::invoke_result_t<Comparator, T, T>, bool>;

// ! integral
template <typename T, typename = constraints_t<std::is_integral<T>>>
constexpr int bit_num = std::numeric_limits<std::make_unsigned_t<T>>::digits;
template <typename T, unsigned int n>
struct is_nbit { static constexpr bool value = bit_num<T> == n; };
template <typename T, unsigned int n>
static constexpr bool is_nbit_v = is_nbit<T, n>::value;

// ?
template <typename T>
struct safely_multipliable {};
template <>
struct safely_multipliable<int> { using type = long long; };
template <>
struct safely_multipliable<long long> { using type = __int128_t; };
template <>
struct safely_multipliable<float> { using type = float; };
template <>
struct safely_multipliable<double> { using type = double; };
template <>
struct safely_multipliable<long double> { using type = long double; };
template <typename T>
using safely_multipliable_t = typename safely_multipliable<T>::type;

} // namespace suisen

// ! type aliases
using i128 = __int128_t;
using u128 = __uint128_t;
using ll = long long;
using uint = unsigned int;
using ull  = unsigned long long;

template <typename T> using vec  = std::vector<T>;
template <typename T> using vec2 = vec<vec <T>>;
template <typename T> using vec3 = vec<vec2<T>>;
template <typename T> using vec4 = vec<vec3<T>>;

template <typename T>
using pq_greater = std::priority_queue<T, std::vector<T>, std::greater<T>>;
template <typename T, typename U>
using umap = std::unordered_map<T, U>;

// ! macros (capital: internal macro)
#define OVERLOAD2(_1,_2,name,...) name
#define OVERLOAD3(_1,_2,_3,name,...) name
#define OVERLOAD4(_1,_2,_3,_4,name,...) name

#define REP4(i,l,r,s)  for(std::remove_reference_t<std::remove_const_t<decltype(r)>>i=(l);i<(r);i+=(s))
#define REP3(i,l,r)    REP4(i,l,r,1)
#define REP2(i,n)      REP3(i,0,n)
#define REPINF3(i,l,s) for(std::remove_reference_t<std::remove_const_t<decltype(l)>>i=(l);;i+=(s))
#define REPINF2(i,l)   REPINF3(i,l,1)
#define REPINF1(i)     REPINF2(i,0)
#define RREP4(i,l,r,s) for(std::remove_reference_t<std::remove_const_t<decltype(r)>>i=(l)+fld((r)-(l)-1,s)*(s);i>=(l);i-=(s))
#define RREP3(i,l,r)   RREP4(i,l,r,1)
#define RREP2(i,n)     RREP3(i,0,n)

#define rep(...)    OVERLOAD4(__VA_ARGS__, REP4   , REP3   , REP2   )(__VA_ARGS__)
#define rrep(...)   OVERLOAD4(__VA_ARGS__, RREP4  , RREP3  , RREP2  )(__VA_ARGS__)
#define repinf(...) OVERLOAD3(__VA_ARGS__, REPINF3, REPINF2, REPINF1)(__VA_ARGS__)

#define CAT_I(a, b) a##b
#define CAT(a, b) CAT_I(a, b)
#define UNIQVAR(tag) CAT(tag, __LINE__)
#define loop(n) for (std::remove_reference_t<std::remove_const_t<decltype(n)>> UNIQVAR(loop_variable) = n; UNIQVAR(loop_variable) --> 0;)

#define all(iterable) (iterable).begin(), (iterable).end()
#define input(type, ...) type __VA_ARGS__; read(__VA_ARGS__)

// ! I/O utilities

// pair
template <typename T, typename U>
std::ostream& operator<<(std::ostream& out, const std::pair<T, U> &a) {
    return out << a.first << ' ' << a.second;
}
// tuple
template <unsigned int N = 0, typename ...Args>
std::ostream& operator<<(std::ostream& out, const std::tuple<Args...> &a) {
    if constexpr (N >= std::tuple_size_v<std::tuple<Args...>>) {
        return out;
    } else {
        out << std::get<N>(a);
        if constexpr (N + 1 < std::tuple_size_v<std::tuple<Args...>>) {
            out << ' ';
        }
        return operator<<<N + 1>(out, a);
    }
}
// vector
template <typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T> &a) {
    for (auto it = a.begin(); it != a.end();) {
        out << *it;
        if (++it != a.end()) out << ' ';
    }
    return out;
}
inline void print() { std::cout << '\n'; }
template <typename Head, typename... Tail>
inline void print(const Head &head, const Tail &...tails) {
    std::cout << head;
    if (sizeof...(tails)) std::cout << ' ';
    print(tails...);
}
template <typename Iterable>
auto print_all(const Iterable& v, std::string sep = " ", std::string end = "\n") -> decltype(std::cout << *v.begin(), void()) {
    for (auto it = v.begin(); it != v.end();) {
        std::cout << *it;
        if (++it != v.end()) std::cout << sep;
    }
    std::cout << end;
}

// pair
template <typename T, typename U>
std::istream& operator>>(std::istream& in, std::pair<T, U> &a) {
    return in >> a.first >> a.second;
}
// tuple
template <unsigned int N = 0, typename ...Args>
std::istream& operator>>(std::istream& in, std::tuple<Args...> &a) {
    if constexpr (N >= std::tuple_size_v<std::tuple<Args...>>) {
        return in;
    } else {
        return operator>><N + 1>(in >> std::get<N>(a), a);
    }
}
// vector
template <typename T>
std::istream& operator>>(std::istream& in, std::vector<T> &a) {
    for (auto it = a.begin(); it != a.end(); ++it) in >> *it;
    return in;
}
template <typename ...Args>
void read(Args &...args) {
    ( std::cin >> ... >> args );
}

// ! integral utilities

// Returns pow(-1, n)
template <typename T>
constexpr inline int pow_m1(T n) {
    return -(n & 1) | 1;
}
// Returns pow(-1, n)
template <>
constexpr inline int pow_m1<bool>(bool n) {
    return -int(n) | 1;
}

// Returns floor(x / y)
template <typename T>
constexpr inline T fld(const T x, const T y) {
    return (x ^ y) >= 0 ? x / y : (x - (y + pow_m1(y >= 0))) / y;
}
template <typename T>
constexpr inline T cld(const T x, const T y) {
    return (x ^ y) <= 0 ? x / y : (x + (y + pow_m1(y >= 0))) / y;
}

template <typename T, suisen::constraints_t<suisen::is_nbit<T, 32>> = nullptr>
constexpr inline int popcount(const T x) { return __builtin_popcount(x); }
template <typename T, suisen::constraints_t<suisen::is_nbit<T, 64>> = nullptr>
constexpr inline int popcount(const T x) { return __builtin_popcountll(x); }
template <typename T, suisen::constraints_t<suisen::is_nbit<T, 32>> = nullptr>
constexpr inline int count_lz(const T x) { return x ? __builtin_clz(x)   : suisen::bit_num<T>; }
template <typename T, suisen::constraints_t<suisen::is_nbit<T, 64>> = nullptr>
constexpr inline int count_lz(const T x) { return x ? __builtin_clzll(x) : suisen::bit_num<T>; }
template <typename T, suisen::constraints_t<suisen::is_nbit<T, 32>> = nullptr>
constexpr inline int count_tz(const T x) { return x ? __builtin_ctz(x)   : suisen::bit_num<T>; }
template <typename T, suisen::constraints_t<suisen::is_nbit<T, 64>> = nullptr>
constexpr inline int count_tz(const T x) { return x ? __builtin_ctzll(x) : suisen::bit_num<T>; }
template <typename T>
constexpr inline int floor_log2(const T x) { return suisen::bit_num<T> - 1 - count_lz(x); }
template <typename T>
constexpr inline int ceil_log2(const T x)  { return floor_log2(x) + ((x & -x) != x); }
template <typename T>
constexpr inline int kth_bit(const T x, const unsigned int k) { return (x >> k) & 1; }
template <typename T>
constexpr inline int parity(const T x) { return popcount(x) & 1; }

struct all_subset {
    struct all_subset_iter {
        const int s; int t;
        constexpr all_subset_iter(int s) : s(s), t(s + 1) {}
        constexpr auto operator*() const { return t; }
        constexpr auto operator++() {}
        constexpr auto operator!=(std::nullptr_t) { return t ? (--t &= s, true) : false; }
    };
    int s;
    constexpr all_subset(int s) : s(s) {}
    constexpr auto begin() { return all_subset_iter(s); }
    constexpr auto end()   { return nullptr; }
};

// ! container

template <typename T, typename Comparator, suisen::constraints_t<suisen::is_comparator<Comparator, T>> = nullptr>
auto priqueue_comp(const Comparator comparator) {
    return std::priority_queue<T, std::vector<T>, Comparator>(comparator);
}

template <typename Iterable>
auto isize(const Iterable &iterable) -> decltype(int(iterable.size())) {
    return iterable.size();
}

template <typename T, typename Gen, suisen::constraints_t<suisen::is_same_as_invoke_result<T, Gen, int>> = nullptr>
auto generate_vector(int n, Gen generator) {
    std::vector<T> v(n);
    for (int i = 0; i < n; ++i) v[i] = generator(i);
    return v;
}

template <typename T>
void sort_unique_erase(std::vector<T> &a) {
    std::sort(a.begin(), a.end());
    a.erase(std::unique(a.begin(), a.end()), a.end());
}

// ! other utilities

// x <- min(x, y). returns true iff `x` has chenged.
template <typename T>
inline bool chmin(T &x, const T &y) {
    if (y >= x) return false;
    x = y;
    return true;
}
// x <- max(x, y). returns true iff `x` has chenged.
template <typename T>
inline bool chmax(T &x, const T &y) {
    if (y <= x) return false;
    x = y;
    return true;
}

namespace suisen {}
using namespace suisen;
using namespace std;

struct io_setup {
    io_setup(int precision = 20) {
        std::ios::sync_with_stdio(false);
        std::cin.tie(nullptr);
        std::cout << std::fixed << std::setprecision(precision);
    }
} io_setup_ {};

// ! code from here

#include <algorithm>
#include <cassert>
#include <vector>

namespace atcoder {

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

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

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

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

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

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

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

}  // namespace atcoder

int main() {
    input(int, n, w, h);
    atcoder::dsu uf(h + w);
    loop(n) {
        input(int, y, x);
        --x, --y;
        uf.merge(x, h + y);
    }
    bool b = false;
    int cnt = 0;
    for (const auto &g : uf.groups()) {
        if (g.size() == 1) continue;
        ++cnt;
        for (int e : g) {
            if (e < h) {
                b |= e == 0;
                b |= e == h - 1;
            } else {
                e -= h;
                b |= e == 0;
                b |= e == w - 1;
            }
        }
    }
    if (cnt == 1) {
        print(n - cnt);
    } else {
        print(n + cnt - 1 - b);
    }
    return 0;
}
accuracy: 1
time_ms: 10
memory_kb: 15056
final_rank: 13

code_snippet: // #include "atcoder/all"
#include <iostream> // cout, endl, cin
#include <string> // string, to_string, stoi
#include <vector> // vector
#include <algorithm> // min, max, swap, sort, reverse, lower_bound, upper_bound
#include <utility> // pair, make_pair
#include <tuple> // tuple, make_tuple
#include <cstdint> // int64_t, int*_t
#include <cstdio> // printf
#include <map> // map
#include <queue> // queue, priority_queue
#include <set> // set
#include <stack> // stack
#include <deque> // deque
#include <unordered_map> // unordered_map
#include <unordered_set> // unordered_set
#include <bitset> // bitset
#include <cctype> // isupper, islower, isdigit, toupper, tolower
#include <iomanip> // setprecision
#include <complex> // complex
#include <math.h>
#include <functional>
#include <cassert>
using namespace std;
// using namespace atcoder;
using ll = long long;
using P = pair<ll,ll>;
constexpr ll INF = 1e15;
constexpr ll LLMAX = 9223372036854775807;
constexpr int inf = 1e9;
constexpr ll mod = 1000000007;
// constexpr ll mod = 998244353;
// 右下左上
const int dx[8] = {1, 0, -1, 0,1,1,-1,-1};
const int dy[8] = {0, 1, 0, -1,1,-1,1,-1};
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; }
#define eol "\n"
// ---------------------------------------------------------------------------


struct UnionFind {
    vector<int> par, w;

    UnionFind(int n) : par(n, -1), w(n, 0) { }
    void init(int n) { par.assign(n, -1); w.assign(n, 0); }

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

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

    bool merge(int x, int y) {
        x = root(x); y = root(y);
        if (x == y) {
            ++w[x];
            return false;
        }
        if (par[x] > par[y]) swap(x, y); // merge technique
        par[x] += par[y];
        par[y] = x;
        w[x] += w[y];
        ++w[x];
        return true;
    }

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

    int wei(int x) {
        return w[root(x)];
    }
};


bool solve(){
    int N,W,H;
    cin >> N >> W >> H;
    if(N == 0) return false;
    vector<vector<int>> SW(W),SH(H);
    bool ext = false;
    for(int i=0; i<N; i++){
        int x,y;
        cin >> x >> y;
        x--;y--;
        SW[x].emplace_back(i);
        SH[y].emplace_back(i);
        if(x == 0) ext = true;
        if(y == 0) ext = true;
        if(x == W-1) ext = true;
        if(y == H-1) ext = true;
    }
    UnionFind uf(N);
    for(int i=0; i<W; i++){
        for(int j=1; j<SW[i].size(); j++){
            uf.merge(SW[i][j],SW[i][j-1]);
        }
    }
    for(int i=0; i<H; i++){
        for(int j=1; j<SH[i].size(); j++){
            uf.merge(SH[i][j],SH[i][j-1]);
        }
    }
    int root = 0;
    for(int i=0; i<N; i++){
        if(uf.root(i) == i) root++;
    }
    if(root == 1){
        cout << N-1 << endl;
    }else{
        int ans = N-1;
        ans += root-1;
        if(!ext) ans++;
        cout << ans << endl;
    }
    return true;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // while(solve());
    solve();
    return 0;
}
accuracy: 1
time_ms: 10
memory_kb: 10176
final_rank: 4