//By Vasu Agrawal
#include<iostream>
#include<bits/stdc++.h>
typedef long long int ll;
#define pb push_back
#define pob pop_back
#define mk make_pair
#define fr first
#define sc second
#define pu push
#define ins insert
#define mod 1000000007
#define mem(a,val) memset(a,val,sizeof(a))
#define V(a) vector<a>
#define pp pair<ll,ll>
#define lb(a,n) lower_bound(a.begin(),a.end(),n)-a.begin()
#define ub(a,n) upper_bound(a.begin(),a.end(),n)-a.begin()
#define F(i,n) for(ll i=0;i<n;i++)
#define in(a,n) F(i,n) cin>>a[i]
#define in1(a,n) f(i,n) cin>>a[i]
#define out1(a,n); f(i,n) cout<<a[i]<<" ";cout<<endl;
#define out(a,n); F(i,n) cout<<a[i]<<" ";cout<<endl;
#define f(i,n) for(ll i=1;i<=n;i++)
#define vasu2907 ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
bool isprime(ll n) //Time Complexity--->sqrt(n)
{   if (n <= 1)  return false; if (n <= 3)  return true;
  	if (n%2 == 0 || n%3 == 0) return false;
    for (int i=5; i*i<=n; i=i+6) if (n%i == 0 || n%(i+2) == 0) return false;
    return true;
}
vector<ll> prime;
void sieve(ll n)
{	bool bakh[n+1];memset(bakh, true, sizeof(bakh));
    for (ll p=2; p*p<=n; p++)
    {	if (bakh[p] == true)
        {	for (ll i=p*p; i<=n; i += p)    bakh[i] = false; } }
  	for (ll p=2; p<=n; p++)    if (bakh[p])
	  	prime.pb(p);
}//Taken from https://www.geeksforgeeks.org/eulers-totient-function-for-all-numbers-smaller-than-or-equal-to-n/
ll eulertotient(ll z)   //Time--->n/logn
{	ll fac=z;
	for(ll i=0;prime[i]*prime[i]<=z;i++)
	{	if(z%prime[i]==0)
		{	fac-=(fac/prime[i]);	while(z%prime[i]==0)	z/=prime[i];	}
	}	if(z>1)	fac-=(fac/z);
	return fac;
}
//Taken from https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
ll power(ll x,ll y,ll p)
{	ll res = 1; x = x % p;	while (y > 0)  {	if (y & 1)	res = (res*x) % p; 	y = y>>1;	x = (x*x) % p; }return res; }
//Taken from https://www.geeksforgeeks.org/euclidean-algorithms-basic-and-extended/
ll gcd(ll a,ll b)
{	if (a == 0)    return b; 	return gcd(b % a, a); }
ll lcm(ll a,ll b)
{	ll g=gcd(a,b); ll ans=(a*b)/g; return ans;	}
//Taken from https://www.geeksforgeeks.org/multiplicative-inverse-under-modulo-m/
ll modInverse(ll a, ll m)
{	ll hvf=gcd(a,m);	if(hvf==1)	return power(a,m-2,m);	return -1;}
void multiply(ll F[2][2], ll M[2][2])
{ ll x =  F[0][0]*M[0][0] + F[0][1]*M[1][0];  ll y =  F[0][0]*M[0][1] + F[0][1]*M[1][1];
  ll z =  F[1][0]*M[0][0] + F[1][1]*M[1][0];  ll w =  F[1][0]*M[0][1] + F[1][1]*M[1][1];
  F[0][0] = x;   F[0][1] = y;   F[1][0] = z;   F[1][1] = w;
}
void powermat(ll F[2][2], ll n)
{	if( n == 0 || n == 1)
    return; ll M[2][2] = {{1,1},{1,0}};   powermat(F, n/2);   multiply(F, F);   if (n%2 != 0)      multiply(F, M);
}
ll fib(ll n)
{ ll F[2][2] = {{1,1},{1,0}};
  if (n == 0) return 0; powermat(F, n-1);   return F[0][0];
}
V(ll) merge(V(ll) v1,V(ll) v2)
{	V(ll) temp;
	ll i=0,j=0;
	while(i<v1.size() && j<v2.size())
	{	if(v1[i]<v2[j])
			temp.pb(v1[i]),i++;
		else
			temp.pb(v2[j]),j++;
	}
	while(i<v1.size())
		temp.pb(v1[i]),i++;
	while(j<v2.size())
		temp.pb(v2[j]),j++;
	return temp;
}
//ll root(ll x)
//{	while(par[x]!=x)
//	{	par[x]=par[par[x]];
//		x=par[x];
//	}
//	return x;
//}
//void uni(ll x,ll y)
//{	x=root(x);
//	y=root(y);
//	if(val[x]<=val[y])
//		par[y]=par[x];
//	else
//		par[x]=par[y];
//}

//id=(id&(id+1))-1
//ll fetch(ll id)
//{
//}
//id=(id|(id+1))
//void update(ll id)
//{
//}

//freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
// Code Starts Here
V(ll) mark;
V(V(ll)) adj;
void dfs(ll u)
{	mark[u]=1;
	for(auto v:adj[u])
	{	if(!mark[v])	dfs(v);
	}
}
int main()
{	vasu2907;
	ll n,m,q,a,b;
	cin>>n>>m>>q;
	adj.resize(n+m+1);
	mark.resize(n+m+1);
	while(q--)
	{	cin>>a>>b;
		adj[a].pb(n+b);
		adj[n+b].pb(a);
	}
	ll ans=-1;
	for(ll i=1;i<=n+m;i++)
	{	if(!mark[i])	dfs(i),ans+=1;
	}
	cout<<ans<<endl;
}


//By Vasu Agrawal
#include<iostream>
#include<bits/stdc++.h>
typedef long long int ll;
#define pb push_back
#define pob pop_back
#define mk make_pair
#define fr first
#define sc second
#define pu push
#define ins insert
#define mod 1000000007
#define mem(a,val) memset(a,val,sizeof(a))
#define V(a) vector<a>
#define pp pair<ll,ll>
#define lb(a,n) lower_bound(a.begin(),a.end(),n)-a.begin()
#define ub(a,n) upper_bound(a.begin(),a.end(),n)-a.begin()
#define F(i,n) for(ll i=0;i<n;i++)
#define in(a,n) F(i,n) cin>>a[i]
#define in1(a,n) f(i,n) cin>>a[i]
#define out1(a,n); f(i,n) cout<<a[i]<<" ";cout<<endl;
#define out(a,n); F(i,n) cout<<a[i]<<" ";cout<<endl;
#define f(i,n) for(ll i=1;i<=n;i++)
#define vasu2907 ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
using namespace std;
bool isprime(ll n) //Time Complexity--->sqrt(n)
{   if (n <= 1)  return false; if (n <= 3)  return true;
  	if (n%2 == 0 || n%3 == 0) return false;
    for (int i=5; i*i<=n; i=i+6) if (n%i == 0 || n%(i+2) == 0) return false;
    return true;
}
vector<ll> prime;
void sieve(ll n)
{	bool bakh[n+1];memset(bakh, true, sizeof(bakh));
    for (ll p=2; p*p<=n; p++)
    {	if (bakh[p] == true)
        {	for (ll i=p*p; i<=n; i += p)    bakh[i] = false; } }
  	for (ll p=2; p<=n; p++)    if (bakh[p])
	  	prime.pb(p);
}//Taken from https://www.geeksforgeeks.org/eulers-totient-function-for-all-numbers-smaller-than-or-equal-to-n/
ll eulertotient(ll z)   //Time--->n/logn
{	ll fac=z;
	for(ll i=0;prime[i]*prime[i]<=z;i++)
	{	if(z%prime[i]==0)
		{	fac-=(fac/prime[i]);	while(z%prime[i]==0)	z/=prime[i];	}
	}	if(z>1)	fac-=(fac/z);
	return fac;
}
//Taken from https://www.geeksforgeeks.org/modular-exponentiation-power-in-modular-arithmetic/
ll power(ll x,ll y,ll p)
{	ll res = 1; x = x % p;	while (y > 0)  {	if (y & 1)	res = (res*x) % p; 	y = y>>1;	x = (x*x) % p; }return res; }
//Taken from https://www.geeksforgeeks.org/euclidean-algorithms-basic-and-extended/
ll gcd(ll a,ll b)
{	if (a == 0)    return b; 	return gcd(b % a, a); }
ll lcm(ll a,ll b)
{	ll g=gcd(a,b); ll ans=(a*b)/g; return ans;	}
//Taken from https://www.geeksforgeeks.org/multiplicative-inverse-under-modulo-m/
ll modInverse(ll a, ll m)
{	ll hvf=gcd(a,m);	if(hvf==1)	return power(a,m-2,m);	return -1;}
void multiply(ll F[2][2], ll M[2][2])
{ ll x =  F[0][0]*M[0][0] + F[0][1]*M[1][0];  ll y =  F[0][0]*M[0][1] + F[0][1]*M[1][1];
  ll z =  F[1][0]*M[0][0] + F[1][1]*M[1][0];  ll w =  F[1][0]*M[0][1] + F[1][1]*M[1][1];
  F[0][0] = x;   F[0][1] = y;   F[1][0] = z;   F[1][1] = w;
}
void powermat(ll F[2][2], ll n)
{	if( n == 0 || n == 1)
    return; ll M[2][2] = {{1,1},{1,0}};   powermat(F, n/2);   multiply(F, F);   if (n%2 != 0)      multiply(F, M);
}
ll fib(ll n)
{ ll F[2][2] = {{1,1},{1,0}};
  if (n == 0) return 0; powermat(F, n-1);   return F[0][0];
}
V(ll) merge(V(ll) v1,V(ll) v2)
{	V(ll) temp;
	ll i=0,j=0;
	while(i<v1.size() && j<v2.size())
	{	if(v1[i]<v2[j])
			temp.pb(v1[i]),i++;
		else
			temp.pb(v2[j]),j++;
	}
	while(i<v1.size())
		temp.pb(v1[i]),i++;
	while(j<v2.size())
		temp.pb(v2[j]),j++;
	return temp;
}
//ll root(ll x)
//{	while(par[x]!=x)
//	{	par[x]=par[par[x]];
//		x=par[x];
//	}
//	return x;
//}
//void uni(ll x,ll y)
//{	x=root(x);
//	y=root(y);
//	if(val[x]<=val[y])
//		par[y]=par[x];
//	else
//		par[x]=par[y];
//}

//id=(id&(id+1))-1
//ll fetch(ll id)
//{
//}
//id=(id|(id+1))
//void update(ll id)
//{
//}

//freopen("input.txt", "r", stdin);
//freopen("output.txt", "w", stdout);
// Code Starts Here
V(ll) mark;
V(V(ll)) adj;
void dfs(ll u)
{	mark[u]=1;
	for(auto v:adj[u])
	{	if(!mark[v])	dfs(v);
	}
}
int main()
{	vasu2907;
	ll n,m,q,a,b;
	cin>>n>>m>>q;
	adj.resize(n+m+1);
	mark.resize(n+m+1);
	while(q--)
	{	cin>>a>>b;
		adj[a].pb(a+b);
		adj[a+b].pb(a);
	}
	ll ans=-1;
	for(ll i=1;i<=n+m;i++)
	{	if(!mark[i])	dfs(i),ans+=1;
	}
	cout<<ans<<endl;
}


//
//  main.cpp
//  1012B
//
//  Created by Sandulescu Sorana on 02.08.2018.
//  Copyright © 2018 Sandulescu Alex. All rights reserved.
//

#include <iostream>
#include <vector>

using namespace std;

vector<int> graf[100003];

int N, M, Q, x, y, cnt, visit[100003];
void df(int x) {
    visit[x] = true;
    for(auto i = graf[x].begin(); i != graf[x].end(); i++) {
        if(!visit[*i]) df(*i);
    }
}

int main()
{
    cin >> N >> M >> Q;
    for(int i = 1; i <= Q; i++) {
        cin >> x >> y;
        graf[x - 1].push_back(y + N - 1);
        graf[y + N - 1].push_back(x - 1);
    }

    for(int i = 1; i <= N + M; i++)
        if(visit[i] == false) {
            df(i);
            cnt++;
        }

    cout << cnt - 2 << "\n";
    return 0;
}


//
//  main.cpp
//  1012B
//
//  Created by Sandulescu Sorana on 02.08.2018.
//  Copyright © 2018 Sandulescu Alex. All rights reserved.
//

#include <iostream>
#include <vector>

using namespace std;

vector<int> graf[400003];

int N, M, Q, x, y, cnt, visit[400003];
void df(int x) {
    visit[x] = true;
    for(auto i = graf[x].begin(); i != graf[x].end(); i++) {
        if(!visit[*i]) df(*i);
    }
}

int main()
{
    cin >> N >> M >> Q;
    for(int i = 1; i <= Q; i++) {
        cin >> x >> y;
        graf[x].push_back(y + N);
        graf[y + N].push_back(x);
    }

    for(int i = 1; i <= N + M; i++)
        if(visit[i] == false) {
            df(i);
            cnt++;
        }

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


//
//  main.cpp
//  1012B
//
//  Created by Sandulescu Sorana on 02.08.2018.
//  Copyright © 2018 Sandulescu Alex. All rights reserved.
//

#include <iostream>
#include <vector>

using namespace std;

vector<int> graf[100003];

int N, M, Q, x, y, cnt, visit[100003];
void df(int x) {
    visit[x] = true;
    for(auto i = graf[x].begin(); i != graf[x].end(); i++) {
        if(!visit[*i]) df(*i);
    }
}

int main()
{
    cin >> N >> M >> Q;
    for(int i = 1; i <= Q; i++) {
        cin >> x >> y;
        graf[x - 1].push_back(y + N - 1);
        graf[y + N - 1].push_back(x - 1);
    }

    for(int i = 1; i <= N + M; i++)
        if(visit[i] == false) {
            df(i);
            cnt++;
        }

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


//
//  main.cpp
//  1012B
//
//  Created by Sandulescu Sorana on 02.08.2018.
//  Copyright © 2018 Sandulescu Alex. All rights reserved.
//

#include <iostream>
#include <vector>

using namespace std;

vector<int> graf[100003];

int N, M, Q, x, y, cnt, visit[100003];
void df(int x) {
    visit[x] = true;
    for(auto i = graf[x].begin(); i != graf[x].end(); i++) {
        if(!visit[*i]) df(*i);
    }
}

int main()
{
    cin >> N >> M >> Q;
    for(int i = 1; i <= Q; i++) {
        cin >> x >> y;
        graf[x].push_back(y + N);
        graf[y + N].push_back(x);
    }

    for(int i = 1; i <= N + M; i++)
        if(visit[i] == false) {
            df(i);
            cnt++;
        }

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


//
//  main.cpp
//  1012B
//
//  Created by Sandulescu Sorana on 02.08.2018.
//  Copyright © 2018 Sandulescu Alex. All rights reserved.
//

#include <iostream>
#include <vector>

using namespace std;

vector<int> graf[200003];

int N, M, Q, x, y, cnt, visit[200003];
void df(int x) {
    visit[x] = true;
    for(auto i = graf[x].begin(); i != graf[x].end(); i++) {
        if(!visit[*i]) df(*i);
    }
}

int main()
{
    cin >> N >> M >> Q;
    for(int i = 1; i <= Q; i++) {
        cin >> x >> y;
        graf[x].push_back(y + N);
        graf[y + N].push_back(x);
    }

    for(int i = 1; i <= N + M; i++)
        if(visit[i] == false) {
            df(i);
            cnt++;
        }

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


#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize("unroll-loops")

#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 pb push_back
#define mp make_pair
#define ld long double
#define sz(a) (ll)(a).size()
// #define endl "\n"

typedef tree<pair<ll,ll> ,null_type,less<pair<ll,ll> >,rb_tree_tag,tree_order_statistics_node_update> ordered_set;
//K-th smallest
//cout << k << "kth smallest: " << *A.find_by_order(k-1) << endl;
//NO OF ELEMENTS < X
//cout << "No of elements less than " << X << " are " << A.order_of_key(X) << endl;

const int MAXN = 200005;

ll arr[MAXN], size[MAXN];
vector<ll>adj[MAXN], col[MAXN], row[MAXN];
set<ll>s;

ll parent(ll i)
{
    while(arr[i] != i)
    {
        arr[i] = arr[arr[i]];
		i = arr[i];
    }
	return i;
}

void connect(ll a, ll b)
{
    ll pa = parent(a);
    ll pb = parent(b);
    if(size[pa] < size[pb])
    {
		arr[pa] = arr[pb];
		size[pb] += size[pa];
	}
    else
    {
		arr[pb] = arr[pa];
		size[pa] += size[pb];
	}
}

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

 	ll n, m, q, val = 0;
 	cin >> n >> m >> q;
 	for(int i=0;i<q;i++)
 	{
 		ll u, v;
 		cin >> u >> v;
 		col[v - 1].pb(u - 1);
 		row[u - 1].pb(v - 1);
 	}
 	for(int i=0;i<n;i++) arr[i] = i, size[i] = 1;
 	for(int i=0;i<m;i++) for(int j=1;j<sz(col[i]);j++) connect(col[i][0], col[i][j]);
 	for(int i=0;i<n;i++) s.insert(parent(i));
 	for(int i=0;i<m;i++) if(!sz(col[i])) val++;
 	for(int i=0;i<n;i++) if(!sz(row[i])) val++;
 	cout << val + sz(s) - 1 << endl;
    return 0;
}


#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize("unroll-loops")

#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 pb push_back
#define mp make_pair
#define ld long double
#define sz(a) (ll)(a).size()
// #define endl "\n"

typedef tree<pair<ll,ll> ,null_type,less<pair<ll,ll> >,rb_tree_tag,tree_order_statistics_node_update> ordered_set;
//K-th smallest
//cout << k << "kth smallest: " << *A.find_by_order(k-1) << endl;
//NO OF ELEMENTS < X
//cout << "No of elements less than " << X << " are " << A.order_of_key(X) << endl;

const int MAXN = 200005;

ll arr[MAXN], size[MAXN];
vector<ll>adj[MAXN], col[MAXN], row[MAXN];
set<ll>s;

ll parent(ll i)
{
    while(arr[i] != i)
    {
        arr[i] = arr[arr[i]];
		i = arr[i];
    }
	return i;
}

void connect(ll a, ll b)
{
    ll pa = parent(a);
    ll pb = parent(b);
    if(size[pa] < size[pb])
    {
		arr[pa] = arr[pb];
		size[pb] += size[pa];
	}
    else
    {
		arr[pb] = arr[pa];
		size[pa] += size[pb];
	}
}

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

 	ll n, m, q, val = 0;
 	cin >> n >> m >> q;
 	for(int i=0;i<q;i++)
 	{
 		ll u, v;
 		cin >> u >> v;
 		col[v - 1].pb(u - 1);
 		row[u - 1].pb(v - 1);
 	}
 	for(int i=0;i<n;i++) arr[i] = i, size[i] = 1;
 	for(int i=0;i<m;i++) for(int j=1;j<sz(col[i]);j++) connect(col[i][0], col[i][j]);
 	for(int i=0;i<n;i++) s.insert(parent(i));
 	for(int i=0;i<m;i++) if(!sz(col[i])) val++;
 	cout << val + sz(s) - 1 << endl;
    return 0;
}


#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize("unroll-loops")

#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 pb push_back
#define mp make_pair
#define ld long double
#define sz(a) (ll)(a).size()
// #define endl "\n"

typedef tree<pair<ll,ll> ,null_type,less<pair<ll,ll> >,rb_tree_tag,tree_order_statistics_node_update> ordered_set;
//K-th smallest
//cout << k << "kth smallest: " << *A.find_by_order(k-1) << endl;
//NO OF ELEMENTS < X
//cout << "No of elements less than " << X << " are " << A.order_of_key(X) << endl;

const int MAXN = 200005;

ll arr[MAXN], size[MAXN];
vector<ll>adj[MAXN], col[MAXN], row[MAXN];
set<ll>s;

ll parent(ll i)
{
    while(arr[i] != i)
    {
        arr[i] = arr[arr[i]];
		i = arr[i];
    }
	return i;
}

void connect(ll a, ll b)
{
    ll pa = parent(a);
    ll pb = parent(b);
    if(size[pa] < size[pb])
    {
		arr[pa] = arr[pb];
		size[pb] += size[pa];
	}
    else
    {
		arr[pb] = arr[pa];
		size[pa] += size[pb];
	}
}

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

 	ll n, m, q, val = 0;
 	cin >> n >> m >> q;
 	for(int i=0;i<q;i++)
 	{
 		ll u, v;
 		cin >> u >> v;
 		u--;
 		v--;
 		col[v].pb(u);
 		row[u].pb(v);
 	}
 	for(int i=0;i<n;i++) arr[i] = i, size[i] = 1;
 	for(int i=0;i<m;i++)
 		for(int j=1;j<sz(col[i]);j++) connect(col[i][0], col[i][j]);
 	for(int i=0;i<m;i++)
 	{
 		s.insert(i);
 		if(sz(col[i]))
 			adj[parent(col[i][0])].pb(i);
 	}
 	for(int i=0;i<m;i++)
 	{
 		if(sz(adj[i]))
 		{
 			val++;
 			for(int j=0;j<sz(adj[i]);j++) s.erase(s.find(adj[i][j]));
 		}
 	}
 	for(int i=0;i<n;i++)
 		if(!sz(row[i])) val++;
 	cout << val + sz(s) - 1 << endl;
    return 0;
}


#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize("unroll-loops")

#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 pb push_back
#define mp make_pair
#define ld long double
#define sz(a) (ll)(a).size()
// #define endl "\n"

typedef tree<pair<ll,ll> ,null_type,less<pair<ll,ll> >,rb_tree_tag,tree_order_statistics_node_update> ordered_set;
//K-th smallest
//cout << k << "kth smallest: " << *A.find_by_order(k-1) << endl;
//NO OF ELEMENTS < X
//cout << "No of elements less than " << X << " are " << A.order_of_key(X) << endl;

const int MAXN = 200005;

ll arr[MAXN], size[MAXN];
vector<ll>adj[MAXN], col[MAXN], row[MAXN];
set<ll>s;

ll parent(ll i)
{
    while(arr[i] != i)
    {
        arr[i] = arr[arr[i]];
		i = arr[i];
    }
	return i;
}

void connect(ll a, ll b)
{
    ll pa = parent(a);
    ll pb = parent(b);
    if(size[pa] < size[pb])
    {
		arr[pa] = arr[pb];
		size[pb] += size[pa];
	}
    else
    {
		arr[pb] = arr[pa];
		size[pa] += size[pb];
	}
}

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

 	ll n, m, q, val = 0;
 	cin >> n >> m >> q;
 	for(int i=0;i<q;i++)
 	{
 		ll u, v;
 		cin >> u >> v;
 		col[v - 1].pb(u - 1);
 		row[u - 1].pb(v - 1);
 	}
 	for(int i=0;i<m;i++) for(int j=1;j<sz(col[i]);j++) connect(col[i][0], col[i][j]);
 	for(int i=0;i<n;i++) s.insert(parent(i));
 	for(int i=0;i<m;i++) if(!sz(col[i])) val++;
 	for(int i=0;i<n;i++) if(!sz(row[i])) val++;
 	cout << val + sz(s) - 1 << endl;
    return 0;
}


#include <iostream>
#include <set>

using namespace std;

class DSU {
int n, k;
int* p;
int* w;

public:
DSU(int _n) {
n = _n; k = n;
p = new int[n];
w = new int[n];
for(int i=0; i<n; i++) {
    p[i]=i;
    w[i]=1;
    }
}

int getNum(int a) {
int b=a;
while(p[b]!=b) b=p[b];
while(p[a]!=a) {
    int t = p[a];
    p[a]=b;
    a = t;
    }
return b;
}

void setUnion(int a, int b) {
if (isEqual(a, b)) return;
int p1 = getNum(a);
int p2 = getNum(b);
if (w[p1]<=w[p2]) {
    w[p2]+=w[p1];
    p[p1]=p2;
    }
else {
    w[p1]+=w[p2];
    p[p2]=p1;
    }
k--;
}

bool isEqual(int a, int b) {
return getNum(a)==getNum(b);
}

int getUnionSize(int a) {
return w[getNum(a)];
}

int getUnionCount(void) {
return k;
}
};

int main() {
    ios::sync_with_stdio(0);
    int m, n, q;
    cin>>n>>m>>q;

    DSU d(m+n); //c or m+r

    for(int i=0; i<q; i++) {
        int r, c;
        cin>>r>>c;
        r--; c--;
        d.setUnion(c, m+r);
        }

    cout<<d.getUnionCount()-1;

    return 0;
}


#include <iostream>
#include <set>

using namespace std;

class DSU {
int n;
int* p;
int* w;

public:
DSU(int _n) {
n = _n;
p = new int[n];
w = new int[n];
for(int i=0; i<n; i++) {
    p[i]=i;
    w[i]=1;
    }
}

int getNum(int a) {
int b=a;
while(p[b]!=b) b=p[b];
while(p[a]!=a) {
    int t = p[a];
    p[a]=b;
    a = t;
    }
return b;
}

void setUnion(int a, int b) {
int p1 = getNum(a);
int p2 = getNum(b);
if (w[p1]<=w[p2]) {
    w[p2]+=w[p1];
    p[p1]=p2;
    }
else {
    w[p1]+=w[p2];
    p[p2]=p1;
    }
}

bool isEqual(int a, int b) {
return getNum(a)==getNum(b);
}

int getSize(int a) {
return w[getNum(a)];
}
};

int main() {
    ios::sync_with_stdio(0);
    int m, n, q;
    cin>>n>>m>>q;

    DSU d(m+n); //c or m+r

    int ans=m+n-1;

    for(int i=0; i<q; i++) {
        int r, c;
        cin>>r>>c;
        r--; c--;
        if (!d.isEqual(c, m+r)) {
            d.setUnion(c, m+r);
            ans--;
            }
        }

    cout<<ans;

    return 0;
}


#include <iostream>
#include <set>

using namespace std;

class DSU {
int n;
int* p;
int* w;

public:
DSU(int _n) {
n = _n;
p = new int[n];
w = new int[n];
for(int i=0; i<n; i++) {
    p[i]=i;
    w[i]=1;
    }
}

int getNum(int a) {
int b=a;
while(p[b]!=b) b=p[b];
while(p[a]!=a) {
    int t = p[a];
    p[a]=b;
    a = t;
    }
return b;
}

void setUnion(int a, int b) {
int p1 = getNum(a);
int p2 = getNum(b);
if (w[p1]<=w[p2]) {
    w[p2]+=w[p1];
    p[p1]=p2;
    }
else {
    w[p1]+=w[p2];
    p[p2]=p1;
    }
}

bool isEqual(int a, int b) {
return getNum(a)==getNum(b);
}

int getSize(int a) {
return w[getNum(a)];
}
};

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

    DSU d(m+n); //c or m+r

    int ans=m+n-1;

    for(int i=0; i<q; i++) {
        int r, c;
        cin>>r>>c;
        r--; c--;
        if (!d.isEqual(c, m+r)) {
            d.setUnion(c, m+r);
            ans--;
            }
        }

    cout<<ans;

    return 0;
}


#include <iostream>
#include <set>

using namespace std;

class DSU {
int n;
int* p;
int* w;

public:
DSU(int _n) {
n = _n;
p = new int[n];
w = new int[n];
for(int i=0; i<n; i++) {
    p[i]=i;
    w[i]=1;
    }
}

int getNum(int a) {
int b=a;
while(p[b]!=b) b=p[b];
while(p[a]!=a) {
    int t = p[a];
    p[a]=b;
    a = t;
    }
return b;
}

void setUnion(int a, int b) {
int p1 = getNum(a);
int p2 = getNum(b);
if (w[p1]<=w[p2]) {
    w[p2]+=w[p1];
    p[p1]=p2;
    }
else {
    w[p1]+=w[p2];
    p[p2]=p1;
    }
}

bool isEqual(int a, int b) {
return getNum(a)==getNum(b);
}

int getSize(int a) {
return w[getNum(a)];
}
};

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

    DSU d(m+n); //c or m+r

    for(int i=0; i<q; i++) {
        int r, c;
        cin>>r>>c;
        r--; c--;
        if (!d.isEqual(c, m+r))
            d.setUnion(c, m+r);
        }

    set<int> s;
    for(int i=0; i<m+n; i++) {
        s.insert(d.getNum(i));
        }

    cout<<((int)s.size())-1;

    return 0;
}


#include<bits/stdc++.h>
//#include<inttypes.h>//__int64,__int128..
#define MAX_N 200005
#define MAX_K 105
//vector&pair
#define pb push_back
#define mp make_pair
//math
#define sq(x) ((x)*(x))
#define abs(x) ((x)>0?(x):(-(x)))//Regardless of type
//for pair
#define fi first
#define se second
//infinit
#define LL_inf (1LL<<60)
#define full_inf 0x7fffffff
#define half_inf 0x3fffffff
//MOD
#define MOD 1000000007LL
//operator
#define cpx_mod(x) (((LD)(((LL)x.real())%MOD)),((LD)(((LL)x.imag())%MOD)))
using namespace std;
//type
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pil;
typedef long double LD;
typedef complex<LD> Complex;
int n,m,ans,nn,mm,pa[MAX_N],q;
vector<int> A[MAX_N],B[MAX_N];
int find_pa(int x){return (x==pa[x])?x:(pa[x]=find_pa(pa[x]));}
void merge(int x,int y){
	x=find_pa(x);
	y=find_pa(y);
	if(x==y)	return;
	pa[y]=x;
	mm--;
}
int main(){
	int i,j,x,y;
	scanf("%d %d %d",&n,&m,&q);
	if(!q)	return !printf("%d\n",n+m-1);
	while(q--){
		scanf("%d %d",&x,&y);
		A[y].pb(x);
		B[x].pb(y);
	}
	for(i=1;i<=n;i++)	pa[i]=i;
	mm=n;
	for(i=1;i<=m;i++){
		if(A[i].size())	x=A[i].back();
		for(j=A[i].size()-2;j>=0;j--)	merge(x,A[i][j]);
	}
	nn=mm;
	mm=m;
	for(i=1;i<=m;i++)	pa[i]=i;
	for(i=1;i<=n;i++){
		if(B[i].size())	x=B[i].back();
		for(j=B[i].size()-2;j>=0;j--)	merge(x,B[i][j]);
	}
	printf("%d\n",nn+mm-min(nn,mm)-1);
	return 0;
}


#include<bits/stdc++.h>
//#include<inttypes.h>//__int64,__int128..
#define MAX_N 100005
#define MAX_K 105
//vector&pair
#define pb push_back
#define mp make_pair
//math
#define sq(x) ((x)*(x))
#define abs(x) ((x)>0?(x):(-(x)))//Regardless of type
//for pair
#define fi first
#define se second
//infinit
#define LL_inf (1LL<<60)
#define full_inf 0x7fffffff
#define half_inf 0x3fffffff
//MOD
#define MOD 1000000007LL
//operator
#define cpx_mod(x) (((LD)(((LL)x.real())%MOD)),((LD)(((LL)x.imag())%MOD)))
using namespace std;
//type
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pil;
typedef long double LD;
typedef complex<LD> Complex;
int n,m,ans,nn,mm,pa[MAX_N],q;
vector<int> A[MAX_N],B[MAX_N];
int find_pa(int x){return x==pa[x]?x:pa[x]=find_pa(pa[x]);}
void merge(int x,int y){
	x=find_pa(x);
	y=find_pa(y);
	if(x==y)	return;
	pa[y]=x;
	mm--;
}
int main(){
	int i,j,x,y;
	scanf("%d %d %d",&n,&m,&q);
	if(!q)	return !printf("%d\n",n+m-1);
	while(q--){
		scanf("%d %d",&x,&y);
		A[y].pb(x);
		B[x].pb(y);
	}
	for(i=1;i<=n;i++)	pa[i]=i;
	mm=n;
	for(i=1;i<=m;i++){
		if(A[i].size())	x=A[i].back();
		for(j=A[i].size()-2;j>=0;j--)	merge(x,A[i][j]);
	}
	nn=mm;
	mm=m;
	for(i=1;i<=m;i++)	pa[i]=i;
	for(i=1;i<=n;i++){
		if(B[i].size())	x=B[i].back();
		for(j=B[i].size()-2;j>=0;j--)	merge(x,B[i][j]);
	}
	printf("%d\n",nn+mm-2);
	return 0;
}


#include<bits/stdc++.h>
//#include<inttypes.h>//__int64,__int128..
#define MAX_N 200005
#define MAX_K 105
//vector&pair
#define pb push_back
#define mp make_pair
//math
#define sq(x) ((x)*(x))
#define abs(x) ((x)>0?(x):(-(x)))//Regardless of type
//for pair
#define fi first
#define se second
//infinit
#define LL_inf (1LL<<60)
#define full_inf 0x7fffffff
#define half_inf 0x3fffffff
//MOD
#define MOD 1000000007LL
//operator
#define cpx_mod(x) (((LD)(((LL)x.real())%MOD)),((LD)(((LL)x.imag())%MOD)))
using namespace std;
//type
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pil;
typedef long double LD;
typedef complex<LD> Complex;
int n,m,ans,nn,mm,pa[2*MAX_N],q;
vector<int> A[MAX_N],B[MAX_N];
int find_pa(int x){return (x==pa[x])?x:pa[x]=find_pa(pa[x]);}
void merge(int x,int y){
	x=find_pa(x);
	y=find_pa(y);
	if(x==y)	return;
	pa[y]=x;
	mm--;
}
int main(){
	int i,j,x,y;
	scanf("%d %d %d",&n,&m,&q);
	if(!q)	return !printf("%d\n",n+m-1);
	for(i=1;i<=n+m;i++)	pa[i]=i;
	mm=n+m;
	while(q--){
		scanf("%d %d",&x,&y);
		A[y].pb(x);
		B[x].pb(y);
		merge(x,n+y);
	}
	for(i=1;i<=m;i++){
		if(A[i].size())	x=A[i].back();
		for(j=A[i].size()-2;j>=0;j--)	merge(x,A[i][j]);
	}
	for(i=1;i<=n;i++){
		if(B[i].size())	x=B[i].back();
		for(j=B[i].size()-2;j>=0;j--)	merge(x+n,B[i][j]+n);
	}
	printf("%d\n",mm-1);
	return 0;
}


#include<bits/stdc++.h>
//#include<inttypes.h>//__int64,__int128..
#define MAX_N 200005
#define MAX_K 105
//vector&pair
#define pb push_back
#define mp make_pair
//math
#define sq(x) ((x)*(x))
#define abs(x) ((x)>0?(x):(-(x)))//Regardless of type
//for pair
#define fi first
#define se second
//infinit
#define LL_inf (1LL<<60)
#define full_inf 0x7fffffff
#define half_inf 0x3fffffff
//MOD
#define MOD 1000000007LL
//operator
#define cpx_mod(x) (((LD)(((LL)x.real())%MOD)),((LD)(((LL)x.imag())%MOD)))
using namespace std;
//type
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pil;
typedef long double LD;
typedef complex<LD> Complex;
int n,m,ans,nn,mm,pa[MAX_N],q;
vector<int> A[MAX_N],B[MAX_N];
int find_pa(int x){return x==pa[x]?x:pa[x]=find_pa(pa[x]);}
void merge(int x,int y){
	x=find_pa(x);
	y=find_pa(y);
	if(x==y)	return;
	pa[y]=x;
	mm--;
}
int main(){
	int i,j,x,y;
	scanf("%d %d %d",&n,&m,&q);
	if(!q)	return !printf("%d\n",n+m-1);
	while(q--){
		scanf("%d %d",&x,&y);
		A[y].pb(x);
		B[x].pb(y);
	}
	for(i=1;i<=n;i++)	pa[i]=i;
	mm=n;
	for(i=1;i<=m;i++){
		if(A[i].size())	x=A[i].back();
		for(j=A[i].size()-2;j>=0;j--)	merge(x,A[i][j]);
	}
	nn=mm;
	mm=m;
	for(i=1;i<=m;i++)	pa[i]=i;
	for(i=1;i<=n;i++){
		if(B[i].size())	x=B[i].back();
		for(j=B[i].size()-2;j>=0;j--)	merge(x,B[i][j]);
	}
	printf("%d\n",nn+mm-2);
	return 0;
}


#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PII;

struct Union_Find {
#define maxn 200010
    LL par[maxn], sz;
    void init(LL _sz) {
        sz = _sz;
        memset(par, -1, sizeof(par[0]) * (sz + 1));
    }
    LL Find(LL u) {
        if (par[u] == -1) return u;
        else return Find(par[u]);
    }
    void Union(LL x, LL y) {
//        printf("union %lld %lld\n", x, y);
        LL fx = Find(x), fy = Find(y);
//        printf("fx = %lld fy = %lld\n", fx, fy);
        if (fx != fy)
            par[fx] = fy;
    }
    LL get_count() {
        LL ret = 0;
        for (LL i = 1; i <= sz; ++i) {
            if (par[i] == -1) ++ret;
//            printf("2333 %lld %lld\n", i, par[i]);
        }
        return ret;
    }
};

Union_Find uf_row, uf_col;

LL n, m, q;
vector<PII> V;

void build_row() {
    sort(V.begin(), V.end(), [](const PII &x, const PII &y) { return x.first < y.first; });
    for (LL i = 1; i < V.size(); ++i)
        if (V[i].first == V[i - 1].first)
            uf_col.Union(V[i].second, V[i - 1].second);
}

void build_col() {
    sort(V.begin(), V.end(), [](const PII &x, const PII &y) { return x.second < y.second; });
    for (LL i = 1; i < V.size(); ++i)
        if (V[i].second == V[i - 1].second)
            uf_row.Union(V[i].first, V[i - 1].first);
}

LL get_cnt() {
    vector<PII> vec;
    for (auto pii : V) {
        LL xx = uf_row.Find(pii.first);
        LL yy = uf_col.Find(pii.second);
        PII new_pii = make_pair(xx, yy);
        vec.push_back(new_pii);
    }
    sort(vec.begin(), vec.end());
    return (LL)(unique(vec.begin(), vec.end()) - vec.begin());
}

int main() {
    scanf("%lld%lld%lld", &n, &m, &q);
    uf_row.init(n);
    uf_col.init(m);
    for (LL i = 0; i < q; ++i) {
        LL r, c; scanf("%lld%lld", &r, &c);
        V.emplace_back(make_pair(r, c));
    }
    build_row();
    build_col();
    LL nn = uf_row.get_count(), mm = uf_col.get_count();
//    printf("nn %lld mm %lld\n", nn, mm);
    printf("%lld\n", nn + mm - 1 - get_cnt());
}


#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PII;

struct Union_Find {
#define maxn 200010
    LL par[maxn], sz;
    void init(LL _sz) {
        sz = _sz;
        memset(par, -1, sizeof(par[0]) * (sz + 1));
    }
    LL Find(LL u) {
        if (par[u] == -1) return u;
        else return par[u] = Find(par[u]);
    }
    void Union(LL x, LL y) {
//        printf("union %lld %lld\n", x, y);
        LL fx = Find(x), fy = Find(y);
//        printf("fx = %lld fy = %lld\n", fx, fy);
        if (fx != fy)
            par[fx] = fy;
    }
    LL get_count() {
        LL ret = 0;
        for (LL i = 1; i <= sz; ++i) {
            if (par[i] == -1) ++ret;
//            printf("2333 %lld %lld\n", i, par[i]);
        }
        return ret;
    }
};

Union_Find uf_row, uf_col;

LL n, m, q;
vector<PII> V;

void build_row() {
    sort(V.begin(), V.end(), [](const PII &x, const PII &y) { return x.first < y.first; });
    for (LL i = 1; i < V.size(); ++i)
        if (V[i].first == V[i - 1].first)
            uf_col.Union(V[i].second, V[i - 1].second);
}

void build_col() {
    sort(V.begin(), V.end(), [](const PII &x, const PII &y) { return x.second < y.second; });
    for (LL i = 1; i < V.size(); ++i)
        if (V[i].second == V[i - 1].second)
            uf_row.Union(V[i].first, V[i - 1].first);
}

LL get_cnt() {
    vector<PII> vec;
    for (auto pii : V) {
        LL xx = uf_row.Find(pii.first);
        LL yy = uf_col.Find(pii.second);
        PII new_pii = make_pair(xx, yy);
        vec.push_back(new_pii);
    }
    sort(vec.begin(), vec.end());
    return (LL)(unique(vec.begin(), vec.end()) - vec.begin());
}

int main() {
    scanf("%lld%lld%lld", &n, &m, &q);
    uf_row.init(n);
    uf_col.init(m);
    for (LL i = 0; i < q; ++i) {
        LL r, c; scanf("%lld%lld", &r, &c);
        V.emplace_back(make_pair(r, c));
    }
    build_row();
    build_col();
    LL nn = uf_row.get_count(), mm = uf_col.get_count();
//    printf("nn %lld mm %lld\n", nn, mm);
    printf("%lld\n", nn + mm - 1 - get_cnt());
}


#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> PII;

struct Union_Find {
#define maxn 200010
    LL par[maxn], sz;
    void init(LL _sz) {
        sz = _sz;
        memset(par, -1, sizeof(par[0]) * (sz + 1));
    }
    LL Find(LL u) {
        if (par[u] == -1) return u;
        else return Find(par[u]);
    }
    void Union(LL x, LL y) {
//        printf("union %lld %lld\n", x, y);
        LL fx = Find(x), fy = Find(y);
//        printf("fx = %lld fy = %lld\n", fx, fy);
        if (fx != fy)
            par[fx] = fy;
    }
    LL get_count() {
        LL ret = 0;
        for (LL i = 1; i <= sz; ++i) {
            if (par[i] == -1) ++ret;
//            printf("2333 %lld %lld\n", i, par[i]);
        }
        return ret;
    }
};

Union_Find uf_row, uf_col;

LL n, m, q;
vector<PII> V;

void build_row() {
    sort(V.begin(), V.end(), [](const PII &x, const PII &y) { return x.first < y.first; });
    for (LL i = 1; i < V.size(); ++i)
        if (V[i].first == V[i - 1].first)
            uf_col.Union(V[i].second, V[i - 1].second);
}

void build_col() {
    sort(V.begin(), V.end(), [](const PII &x, const PII &y) { return x.second < y.second; });
    for (LL i = 1; i < V.size(); ++i)
        if (V[i].second == V[i - 1].second)
            uf_row.Union(V[i].first, V[i - 1].first);
}

LL get_cnt() {
    LL ret = 0;
    set<PII> st;
    for (auto pii : V) {
        LL xx = uf_row.Find(pii.first);
        LL yy = uf_col.Find(pii.second);
        PII new_pii = make_pair(xx, yy);
        if (st.find(new_pii) == st.end()) {
            st.insert(new_pii);
            ++ret;
        }
    }
    return ret;
}

int main() {
    scanf("%lld%lld%lld", &n, &m, &q);
    uf_row.init(n);
    uf_col.init(m);
    for (LL i = 0; i < q; ++i) {
        LL r, c; scanf("%lld%lld", &r, &c);
        V.emplace_back(make_pair(r, c));
    }
    build_row();
    build_col();
    LL nn = uf_row.get_count(), mm = uf_col.get_count();
//    printf("nn %lld mm %lld\n", nn, mm);
    printf("%lld\n", nn + mm - 1 - get_cnt());
}


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <set>
#include <map>
#include <string>
#include <vector>

#define MAXN (int)1e7
#define ll long long
#define f first
#define s second
#define INF (int)(1e9 + 7)
#define pb push_back
#define mp make_pair

using namespace std;

ll cnt, m, q, n;
bool was[500000], was2[500000];
vector <int> g1[500000], g2[500000];

void dfs(int x)
{
	was[x] = true;
	for (int i = 0; i < g1[x].size(); ++i)
	{
		if (!was2[g1[x][i]])
		{
			was2[g1[x][i]] = true;
			for (int j = 0; j < g2[g1[x][i]].size(); ++j)
				if (!was[g2[g1[x][i]][j]])
					dfs(g2[g1[x][i]][j]);
		}
	}
}

void dfs2(int x)
{
	was2[x] = true;
	for (int i = 0; i < g2[x].size(); ++i)
	{
		if (!was[g2[x][i]])
		{
			was[g2[x][i]] = true;

			for (int j = 0; j < g1[g2[x][i]].size(); ++j)
				if (!was2[g1[g2[x][i]][j]])
					dfs2(g1[g2[x][i]][j]);
		}
	}
}

int main(){

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

    cin >> n >> m >> q; //>>s>>f;

    for (int i = 1; i <= q; i++)
    {
    	int x, y;
    	cin >> x >> y;
    	g1[x].pb(y);
    	g2[y].pb(x);
    }

    for (int i = 1; i <= n; i++)
    {
    	if (was[i] == false){
    		cnt++;
    		dfs(i);
    	}
    }

    for (int i = 1; i <= m; i++)
    {
    	if (was2[i] == false){
    		cnt++;
    		dfs2(i);
    	}
    }

    cout << cnt - 1 << endl;

    return 0;
}


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <set>
#include <map>
#include <string>
#include <vector>

#define MAXN (int)1e7
#define ll long long
#define f first
#define s second
#define INF (int)(1e9 + 7)
#define pb push_back
#define mp make_pair

using namespace std;

ll cnt, m, q, n;
bool was[500000], was2[500000];
vector <int> g1[500000], g2[500000];

void dfs(int x)
{
	if (was[x] == true)
		return;

	was[x] = true;
	for (int i = 0; i < g1[x].size(); i++)
	{
		was2[g1[x][i]] = true;
		for (int j = 0; j < g2[g1[x][i]].size(); j++)
			dfs(g2[g1[x][i]][j]);
	}
}

void dfs2(int x)
{
	if (was2[x] == true)
		return;

	was2[x] = true;
	for (int i = 0; i < g2[x].size(); i++)
	{
		was[g2[x][i]] = true;
		for (int j = 0; j < g1[g2[x][i]].size(); j++)
			dfs2(g1[g2[x][i]][j]);
	}
}

int main(){

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

    cin >> n >> m >> q; //>>s>>f;

    for (int i = 1; i <= q; i++)
    {
    	int x, y;
    	cin >> x >> y;
    	g1[x].pb(y);
    	g2[y].pb(x);
    }

    for (int i = 1; i <= n; i++)
    {
    	if (was[i] == false){
    		cnt++;
    		dfs(i);
    	}
    }

    for (int i = 1; i <= m; i++)
    {
    	if (was2[i] == false){
    		cnt++;
    		dfs2(i);
    	}
    }

    cout << cnt - 1 << endl;

    return 0;
}


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <set>
#include <map>
#include <string>
#include <vector>

#define MAXN (int)1e7
#define ll long long
#define f first
#define s second
#define INF (int)(1e9 + 7)
#define pb push_back
#define mp make_pair

using namespace std;

ll cnt, m, q, n;
bool was[500000], was2[500000];
vector <int> g1[500000], g2[500000];

void dfs(int x)
{
	if (was[x] == true)
		return;

	was[x] = true;
	for (int i = 0; i < g1[x].size(); i++)
	{
		was2[g1[x][i]] = true;
		for (int j = 0; j < g2[g1[x][i]].size(); j++)
			dfs(g2[g1[x][i]][j]);
	}
}

void dfs2(int x)
{
	if (was2[x] == true)
		return;

	was2[x] = true;
	for (int i = 0; i < g2[x].size(); i++)
	{
		was[g2[x][i]] = true;
		for (int j = 0; j < g1[g2[x][i]].size(); j++)
			dfs2(g1[g2[x][i]][j]);
	}
}

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

    cin >> n >> m >> q; //>>s>>f;

    for (int i = 1; i <= q; i++)
    {
    	int x, y;
    	cin >> x >> y;
    	g1[x].pb(y);
    	g2[y].pb(x);
    }

    for (int i = 1; i <= n; i++)
    {
    	if (was[i] == false){
    		cnt++;
    		dfs(i);
    	}
    }

    for (int i = 1; i <= m; i++)
    {
    	if (was2[i] == false){
    		cnt++;
    		dfs2(i);
    	}
    }

    cout << cnt - 1 << endl;

    return 0;
}


#include<bits/stdc++.h>
using namespace std;
int N,M,Q,col[1<<19],cnts;vector<int>X[1<<19];
void dfs(int pos)
{
	if(col[pos]>=1)return;
	col[pos]=cnts;
	for(int i=0;i<X[pos].size();i++)
	dfs(X[pos][i]);
}
int main()
{
	cin>>N>>M>>Q;
	for(int i=1;i<=Q;i++)
	{
		int cx,cy;
		cin>>cx>>cy;
		X[cx].push_back(cy+N);
		X[cy+N].push_back(cx);
	}
	for(int i=1;i<=N+M;i++)
	{
		if(col[i]>=1)continue;
		cnts++;
		dfs(i);
	}
	cout<<cnts-1;
	return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define aah ios::sync_with_stdio(0)
int f[400005];
int ahhah(int x)
{
    return x==f[x]?f[x]:f[x]=ahhah(f[x]);
}
void match(int x,int y)
{
    int fax=ahhah(x);
    int fay=ahhah(y);
    f[fay]=fax;
}
int main()
{
	aah;
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n+m;i++)
        f[i]=i;
    int x,y;
    for(int i=1;i<=k;i++)
    {
        cin>>x>>y;
        match(x,y+n);
    }
    int ans=-1;
    for(int i=1;i<=n+m;i++)
        if(f[i]==i)
            ans++;
    cout<<ans;
    return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define aah ios::sync_with_stdio(0)
int fa[400005];
int finds(int x)
{
    return x==fa[x]?fa[x]:fa[x]=finds(fa[x]);
}
void match(int x,int y)
{
    int fax=finds(x);
    int fay=finds(y);
    fa[fay]=fax;
}
int main()
{
	aah;
    int n,m,k;
    cin>>n>>m>>k;
    for(int i=1;i<=n+m;i++)
        fa[i]=i;
    int x,y;
    for(int i=1;i<=k;i++)
    {
        cin>>x>>y;
        match(x,y+n);
    }
    int ans=-1;
    for(int i=1;i<=n+m;i++)
        if(fa[i]==i)
            ans++;
    cout<<ans;
    return 0;
}


#include <bits/stdc++.h>

using namespace std;

const int N = 400400;
int n, m, k;
vector<int> g[N];
bool used[N];

void dfs(int v) {
	used[v] = 1;
	for (int u : g[v]) {
		if (used[u]) continue;
		dfs(u);
	}
}

int main(){
	scanf("%d%d%d", &n, &m, &k);
	while(k--) {
		int v, u;
		scanf("%d%d", &v, &u);
		v--;
		u += n - 1;
		g[v].push_back(u);
		g[u].push_back(v);
	}
	int ans = -1;
	for (int v = 0; v < n + m; v++) {
		if (used[v]) continue;
		dfs(v);
		ans++;
	}
	printf("%d\n", ans);
	return 0;
}


#include <bits/stdc++.h>

using namespace std;

const int N = 400400;
int n, m, k;
vector<int> g[N];
bool used[N];

void dfs(int v){
	used[v] = 1;
	for (int u : g[v])
		if (!used[u])
			dfs(u);
}

int main(){
	cin >> n >> m >> k;
	while(k--){
		int v, u;
		cin >> v >> u;
		v--;
		u += n - 1;
		g[v].push_back(u);
		g[u].push_back(v);
	}
	int ans = -1;
	for (int v = 0; v < n + m; v++)
		if (!used[v]){
			dfs(v);
			ans++;
		}
	cout << ans;
	return 0;
}


#include <bits/stdc++.h>

using namespace std;

const int N = 400001;
int n, m, k;
vector<int> g[N];
bool used[N];

void dfs(int v) {
	used[v] = true;
	for (int u : g[v])
		if (!used[u])
			dfs(u);
}

int main(){
	cin >> n >> m >> k;
	while (k--){
		int v, u;
		cin >> u >> v;
		g[v - 1].push_back(u + n - 1);
		g[u + n - 1].push_back(v - 1);
	}
	int ans = -1;
	for (int v = 0; v < n + m; v++)
		if (!used[v]){
			dfs(v);
			ans++;
		}
	cout << ans;
	return 0;
}


#include <bits/stdc++.h>

using namespace std;

const int N = 400400;
int n, m, k;
vector<int> g[N];
bool used[N];

void dfs(int v) {
	used[v] = true;
	for (int u : g[v])
		if (!used[u])
			dfs(u);
}

int main(){
	cin >> n >> m >> k;
	while (k--){
		int v, u;
		cin >> u >> v;
		g[v - 1].push_back(u + n - 1);
		g[u + n - 1].push_back(v - 1);
	}
	int ans = -1;
	for (int v = 0; v < n + m; v++)
		if (!used[v]){
			dfs(v);
			ans++;
		}
	cout << ans;
	return 0;
}


#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize ("O3")
#pragma GCC target ("sse4")
#include <bits/stdc++.h>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> Tree;

const double PI = 4 * atan(1);

#define sz(x) (int)(x).size()
#define ll long long
#define ld long double
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define pii pair <int, int>
#define vi vector<int>
#define f first
#define s second
#define lb lower_bound
#define ub upper_bound
#define all(x) x.begin(), x.end()
#define vpi vector<pair<int, int>>
#define vpd vector<pair<double, double>>
#define pd pair<double, double>

#define f0r(i,a) for(int i=0;i<a;i++)
#define f1r(i,a,b) for(int i=a;i<b;i++)

void fast_io(){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
}
void io(string taskname){
    string fin = taskname + ".in";
    string fout = taskname + ".out";
    const char* FIN = fin.c_str();
    const char* FOUT = fout.c_str();
    freopen(FIN, "r", stdin);
    freopen(FOUT, "w", stdout);
    fast_io();
}
const int MAX = 2e5 + 5;
template<int SZ> struct DSU{
    int par[SZ], sz[SZ];
    DSU(){
        for(int i = 0; i<SZ; i++){
            par[i] = i;
            sz[i] = 1;
        }
    }
    int get(int x){
        if(par[x] != x){
            par[x] = get(par[x]);
        }
        return par[x];
    }
    bool unite(int x, int y){
        x = get(x);
        y = get(y);
        if(x == y){
            return false;
        }
        if(sz[x] < sz[y]){
            swap(x, y);
        }
        sz[x] += sz[y];
        par[y] = x;
        return true;
    }
};
DSU<MAX> row;
DSU<MAX> col;

vi c[MAX];
vi r[MAX];
int main(){
    fast_io();
    int n, m, q;
    cin >> n >> m >> q;
    f0r(i, q){
        int ri,ci;
        cin >> ri >> ci;
        ri--; ci--;
        r[ri].eb(ci);
        c[ci].eb(ri);
    }
    f0r(i, n){
        for(int x: r[i]){
            col.unite(x, r[i][0]);
        }
    }
    f0r(i, m){
        for(int x: c[i]){
            row.unite(x, c[i][0]);
        }
    }
    map<int, int> mr;
    map<int, int> mc;
    f0r(i, n){
        mr[row.get(i)]++;
    }
    f0r(i, m){
        mc[col.get(i)]++;
    }
    int singler = 0;
    for(auto x: mr){
        if(x.s == 1){
            singler++;
        }
    }
    int singlec = 0;
    for(auto x: mc){
        if(x.s == 1){
            singlec++;
        }
    }
    int multr = sz(mr) - singler;
    int multc = sz(mc) - singlec;
    cout << max(singler, singlec) - 1 + max(multr, multc);
    return 0;
}


#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize ("O3")
#pragma GCC target ("sse4")
#include <bits/stdc++.h>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> Tree;

const double PI = 4 * atan(1);

#define sz(x) (int)(x).size()
#define ll long long
#define ld long double
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define pii pair <int, int>
#define vi vector<int>
#define f first
#define s second
#define lb lower_bound
#define ub upper_bound
#define all(x) x.begin(), x.end()
#define vpi vector<pair<int, int>>
#define vpd vector<pair<double, double>>
#define pd pair<double, double>

#define f0r(i,a) for(int i=0;i<a;i++)
#define f1r(i,a,b) for(int i=a;i<b;i++)

void fast_io(){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
}
void io(string taskname){
    string fin = taskname + ".in";
    string fout = taskname + ".out";
    const char* FIN = fin.c_str();
    const char* FOUT = fout.c_str();
    freopen(FIN, "r", stdin);
    freopen(FOUT, "w", stdout);
    fast_io();
}
const int MAX = 2e5 + 5;
template<int SZ> struct DSU{
    int par[SZ], sz[SZ];
    DSU(){
        for(int i = 0; i<SZ; i++){
            par[i] = i;
            sz[i] = 1;
        }
    }
    int get(int x){
        if(par[x] != x){
            par[x] = get(par[x]);
        }
        return par[x];
    }
    bool unite(int x, int y){
        x = get(x);
        y = get(y);
        if(x == y){
            return false;
        }
        if(sz[x] < sz[y]){
            swap(x, y);
        }
        sz[x] += sz[y];
        par[y] = x;
        return true;
    }
};
DSU<MAX> row;
DSU<MAX> col;

vi c[MAX];
vi r[MAX];
int main(){
    fast_io();
    int n, m, q;
    cin >> n >> m >> q;
    f0r(i, q){
        int ri,ci;
        cin >> ri >> ci;
        ri--; ci--;
        r[ri].eb(ci);
        c[ci].eb(ri);
    }
    f0r(i, n){
        for(int x: r[i]){
            col.unite(x, r[i][0]);
        }
    }
    f0r(i, m){
        for(int x: c[i]){
            row.unite(x, c[i][0]);
        }
    }
    set<int> sr;
    set<int> sc;
    f0r(i, n){
        sr.insert(row.get(i));
    }
    f0r(i, m){
        sc.insert(col.get(i));
    }
    cout << max(sz(sr),sz(sc)) - 1 << endl;

    return 0;
}


#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize ("O3")
#pragma GCC target ("sse4")
#include <bits/stdc++.h>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> Tree;

const double PI = 4 * atan(1);

#define sz(x) (int)(x).size()
#define ll long long
#define ld long double
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define pii pair <int, int>
#define vi vector<int>
#define f first
#define s second
#define lb lower_bound
#define ub upper_bound
#define all(x) x.begin(), x.end()
#define vpi vector<pair<int, int>>
#define vpd vector<pair<double, double>>
#define pd pair<double, double>

#define f0r(i,a) for(int i=0;i<a;i++)
#define f1r(i,a,b) for(int i=a;i<b;i++)

void fast_io(){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
}
void io(string taskname){
    string fin = taskname + ".in";
    string fout = taskname + ".out";
    const char* FIN = fin.c_str();
    const char* FOUT = fout.c_str();
    freopen(FIN, "r", stdin);
    freopen(FOUT, "w", stdout);
    fast_io();
}
const int MAX = 2e5 + 5;
template<int SZ> struct DSU{
    int par[SZ], sz[SZ];
    DSU(){
        for(int i = 0; i<SZ; i++){
            par[i] = i;
            sz[i] = 1;
        }
    }
    int big(int i){
        return(sz[get(i)]);
    }
    int get(int x){
        if(par[x] != x){
            par[x] = get(par[x]);
        }
        return par[x];
    }
    bool unite(int x, int y){
        x = get(x);
        y = get(y);
        if(x == y){
            return false;
        }
        if(sz[x] < sz[y]){
            swap(x, y);
        }
        sz[x] += sz[y];
        par[y] = x;
        return true;
    }
};
DSU<MAX> row;
DSU<MAX> col;

vi c[MAX];
vi r[MAX];
int main(){
    fast_io();
    int n, m, q;
    cin >> n >> m >> q;
    f0r(i, q){
        int ri,ci;
        cin >> ri >> ci;
        ri--; ci--;
        r[ri].eb(ci);
        c[ci].eb(ri);
    }
    f0r(i, n){
        for(int x: r[i]){
            col.unite(x, r[i][0]);
        }
    }
    f0r(i, m){
        for(int x: c[i]){
            row.unite(x, c[i][0]);
        }
    }
    map<int, int> mr;
    map<int, int> mc;
    f0r(i, n){
        mr[row.get(i)]++;
    }
    f0r(i, m){
        mc[col.get(i)]++;
    }
    int badr = 0;
    int badc = 0;
    f0r(i, n){
        if(sz(r[i]) == 0 && row.big(i) == 1){
            badr++;
        }
    }
    f0r(i, m){
        if(sz(c[i]) == 0 && col.big(i) == 1){
            badc++;
        }
    }
    cout << badc << " " << badr << endl;
    int goodr = sz(mr) - badr;
    int goodc = sz(mc) - badc;
    cout << min(goodr, goodc) - 1 + badc + badr + abs(goodr-goodc)<< endl;
    return 0;
}


#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize ("O3")
#pragma GCC target ("sse4")
#include <bits/stdc++.h>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> Tree;

const double PI = 4 * atan(1);

#define sz(x) (int)(x).size()
#define ll long long
#define ld long double
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define pii pair <int, int>
#define vi vector<int>
#define f first
#define s second
#define lb lower_bound
#define ub upper_bound
#define all(x) x.begin(), x.end()
#define vpi vector<pair<int, int>>
#define vpd vector<pair<double, double>>
#define pd pair<double, double>

#define f0r(i,a) for(int i=0;i<a;i++)
#define f1r(i,a,b) for(int i=a;i<b;i++)

void fast_io(){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
}
void io(string taskname){
    string fin = taskname + ".in";
    string fout = taskname + ".out";
    const char* FIN = fin.c_str();
    const char* FOUT = fout.c_str();
    freopen(FIN, "r", stdin);
    freopen(FOUT, "w", stdout);
    fast_io();
}
const int MAX = 2e5 + 5;
template<int SZ> struct DSU{
    int par[SZ], sz[SZ];
    DSU(){
        for(int i = 0; i<SZ; i++){
            par[i] = i;
            sz[i] = 1;
        }
    }
    int big(int i){
        return(sz[get(i)]);
    }
    int get(int x){
        if(par[x] != x){
            par[x] = get(par[x]);
        }
        return par[x];
    }
    bool unite(int x, int y){
        x = get(x);
        y = get(y);
        if(x == y){
            return false;
        }
        if(sz[x] < sz[y]){
            swap(x, y);
        }
        sz[x] += sz[y];
        par[y] = x;
        return true;
    }
};
DSU<MAX> row;
DSU<MAX> col;

vi c[MAX];
vi r[MAX];
int main(){
    fast_io();
    int n, m, q;
    cin >> n >> m >> q;
    f0r(i, q){
        int ri,ci;
        cin >> ri >> ci;
        ri--; ci--;
        r[ri].eb(ci);
        c[ci].eb(ri);
    }
    f0r(i, n){
        for(int x: r[i]){
            col.unite(x, r[i][0]);
        }
    }
    f0r(i, m){
        for(int x: c[i]){
            row.unite(x, c[i][0]);
        }
    }
    map<int, int> mr;
    map<int, int> mc;
    f0r(i, n){
        mr[row.get(i)]++;
    }
    f0r(i, m){
        mc[col.get(i)]++;
    }
    int badr = 0;
    int badc = 0;
    f0r(i, n){
        if(sz(r[i]) == 0 && row.big(i) == 1){
            badr++;
        }
    }
    f0r(i, m){
        if(sz(c[i]) == 0 && col.big(i) == 1){
            badc++;
        }
    }
    //cout << badc << " " << badr << endl;
    int goodr = sz(mr) - badr;
    int goodc = sz(mc) - badc;
    assert(goodr == goodc);
    cout << min(goodr, goodc) - 1 + badc + badr + abs(goodr-goodc)<< endl;
    return 0;
}


#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize ("O3")
#pragma GCC target ("sse4")
#include <bits/stdc++.h>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> Tree;

const double PI = 4 * atan(1);

#define sz(x) (int)(x).size()
#define ll long long
#define ld long double
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define pii pair <int, int>
#define vi vector<int>
#define f first
#define s second
#define lb lower_bound
#define ub upper_bound
#define all(x) x.begin(), x.end()
#define vpi vector<pair<int, int>>
#define vpd vector<pair<double, double>>
#define pd pair<double, double>

#define f0r(i,a) for(int i=0;i<a;i++)
#define f1r(i,a,b) for(int i=a;i<b;i++)

void fast_io(){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
}
void io(string taskname){
    string fin = taskname + ".in";
    string fout = taskname + ".out";
    const char* FIN = fin.c_str();
    const char* FOUT = fout.c_str();
    freopen(FIN, "r", stdin);
    freopen(FOUT, "w", stdout);
    fast_io();
}
const int MAX = 2e5 + 5;
template<int SZ> struct DSU{
    int par[SZ], sz[SZ];
    DSU(){
        for(int i = 0; i<SZ; i++){
            par[i] = i;
            sz[i] = 1;
        }
    }
    int big(int i){
        return(sz[get(i)]);
    }
    int get(int x){
        if(par[x] != x){
            par[x] = get(par[x]);
        }
        return par[x];
    }
    bool unite(int x, int y){
        x = get(x);
        y = get(y);
        if(x == y){
            return false;
        }
        if(sz[x] < sz[y]){
            swap(x, y);
        }
        sz[x] += sz[y];
        par[y] = x;
        return true;
    }
};
DSU<MAX> row;
DSU<MAX> col;

vi c[MAX];
vi r[MAX];
int main(){
    fast_io();
    int n, m, q;
    cin >> n >> m >> q;
    f0r(i, q){
        int ri,ci;
        cin >> ri >> ci;
        ri--; ci--;
        r[ri].eb(ci);
        c[ci].eb(ri);
    }
    f0r(i, n){
        for(int x: r[i]){
            col.unite(x, r[i][0]);
        }
    }
    f0r(i, m){
        for(int x: c[i]){
            row.unite(x, c[i][0]);
        }
    }
    map<int, int> mr;
    map<int, int> mc;
    f0r(i, n){
        mr[row.get(i)]++;
    }
    f0r(i, m){
        mc[col.get(i)]++;
    }
    int badr = 0;
    int badc = 0;
    f0r(i, n){
        if(sz(r[i]) == 0 && row.big(i) == 1){
            badr++;
        }
    }
    f0r(i, m){
        if(sz(c[i]) == 0 && col.big(i) == 1){
            badc++;
        }
    }
    //cout << badc << " " << badr << endl;
    int goodr = sz(mr) - badr;
    int goodc = sz(mc) - badc;
    cout << min(goodr, goodc) - 1 + badc + badr + abs(goodr-goodc)<< endl;
    return 0;
}


#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma GCC optimize ("O3")
#pragma GCC target ("sse4")
#include <bits/stdc++.h>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pbds;
typedef tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update> Tree;

const double PI = 4 * atan(1);

#define sz(x) (int)(x).size()
#define ll long long
#define ld long double
#define mp make_pair
#define pb push_back
#define eb emplace_back
#define pii pair <int, int>
#define vi vector<int>
#define f first
#define s second
#define lb lower_bound
#define ub upper_bound
#define all(x) x.begin(), x.end()
#define vpi vector<pair<int, int>>
#define vpd vector<pair<double, double>>
#define pd pair<double, double>

#define f0r(i,a) for(int i=0;i<a;i++)
#define f1r(i,a,b) for(int i=a;i<b;i++)

void fast_io(){
    ios_base::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
}
void io(string taskname){
    string fin = taskname + ".in";
    string fout = taskname + ".out";
    const char* FIN = fin.c_str();
    const char* FOUT = fout.c_str();
    freopen(FIN, "r", stdin);
    freopen(FOUT, "w", stdout);
    fast_io();
}
const int MAX = 2e5 + 5;
template<int SZ> struct DSU{
    int par[SZ], sz[SZ];
    DSU(){
        for(int i = 0; i<SZ; i++){
            par[i] = i;
            sz[i] = 1;
        }
    }
    int get(int x){
        if(par[x] != x){
            par[x] = get(par[x]);
        }
        return par[x];
    }
    bool unite(int x, int y){
        x = get(x);
        y = get(y);
        if(x == y){
            return false;
        }
        if(sz[x] < sz[y]){
            swap(x, y);
        }
        sz[x] += sz[y];
        par[y] = x;
        return true;
    }
};
DSU<MAX> row;
DSU<MAX> col;

vi c[MAX];
vi r[MAX];
int main(){
    fast_io();
    int n, m, q;
    cin >> n >> m >> q;
    f0r(i, q){
        int ri,ci;
        cin >> ri >> ci;
        ri--; ci--;
        r[ri].eb(ci);
        c[ci].eb(ri);
    }
    f0r(i, n){
        for(int x: r[i]){
            col.unite(x, r[i][0]);
        }
    }
    f0r(i, m){
        for(int x: c[i]){
            row.unite(x, c[i][0]);
        }
    }
    set<int> sr;
    set<int> sc;
    f0r(i, n){
        sr.insert(row.get(i));
    }
    f0r(i, m){
        sc.insert(col.get(i));
    }
    cout << sz(sr) + sz(sc) - 2 << endl;

    return 0;
}


#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> v(500002);
int n,m,q;
vector<bool> vu(200001);

void dfs(int node){
	for (auto& i:v[node]){
		if (!vu[i]){
			vu[i]=true;
			dfs(i);
		}
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> m >> q;
	for (int i=0; i<q; ++i){
		int x,y;
		cin >> x >> y;
		v[x].emplace_back(y+200001);
		v[y+200001].emplace_back(x);
	}
	int ans=-1;
	for (int i=1; i<=n; ++i){
		if (!vu[i]){
			vu[i]=true;
			dfs(i);
			ans++;
		}
	}
	for (int i=200002; i<=m+200001; ++i){
		if (!vu[i]){
			dfs(i);
			ans++;
		}
	}
	cout << ans;
	return 0;
}


#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> X(200001),Y(200001);
int n,m,q;
vector<bool> vu(200001),vu2(200001);

void dfs(int node,bool op){
	if (op){
		for (auto& i:X[node]){
			if (!vu[i]){
				vu[i]=true;
				dfs(i,0);
			}
		}
	}
	else{
		for (auto& i:Y[node]){
			if (!vu2[i]){
				vu2[i]=true;
				dfs(i,1);
			}
		}
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> m >> q;
	for (int i=0; i<q; ++i){
		int x,y;
		cin >> x >> y;
		X[x].emplace_back(y);
		Y[y].emplace_back(x);
	}
	int ans=-1;
	for (int i=1; i<=m; ++i){
		if (!vu[i]){
			vu[i]=true;
			dfs(i,1);
			ans++;
		}
	}
	for (int i=1; i<=n; ++i){
		if (!vu2[i]){
			vu2[i]=true;
			dfs(i,0);
			ans++;
		}
	}
	cout << ans;
	return 0;
}


#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> v(500002);
int n,m,q;
vector<bool> vu(500002);

void dfs(int node){
	for (auto& i:v[node]){
		if (!vu[i]){
			vu[i]=true;
			dfs(i);
		}
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> m >> q;
	for (int i=0; i<q; ++i){
		int x,y;
		cin >> x >> y;
		v[x].emplace_back(y+200001);
		v[y+200001].emplace_back(x);
	}
	int ans=-1;
	for (int i=1; i<=n; ++i){
		if (!vu[i]){
			vu[i]=true;
			dfs(i);
			ans++;
		}
	}
	for (int i=200002; i<=m+200001; ++i){
		if (!vu[i]){
			dfs(i);
			ans++;
		}
	}
	cout << ans;
	return 0;
}


#include <vector>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <map>
#include <set>
#include <queue>
#include <string>
#include <cassert>
#include <stack>

#define INFLL 2000000000000000000
#define INF 2000000000
#define MOD 1000000007
#define BASE 77747
#define PI acos(-1.0)
#define MAXLEN 29

typedef std::pair <int, int> pii;
typedef long long ll;
typedef std::vector <ll> vll;

using namespace std;

int n, m, q;
vector <int> vec[200000];
bool arr[200000];
int p[200000];
int sz[200000];

int get(int a) {
	while (p[a] != a) a = p[a];
	return a;
}
void add(int a, int b) {
	a = get(a);
	b = get(b);
	if (sz[a] < sz[b]) swap(a, b);
	sz[a] += sz[b];
	p[b] = a;
}

int main() {
	cin >> n >> m >> q;
	if (q == 0) {
		cout << n + m - 1 << endl;
		return 0;
	}
	for (int i = 0; i < q; i++) {
		int a, b;
		cin >> a >> b;
		arr[b - 1] = true;
		vec[b - 1].push_back(a - 1);
	}
	for (int i = 0; i < n; i++) {
		sz[i] = 1;
		p[i] = i;
	}
	for (int i = 0; i < n; i++) {
		for (int j = 1; j < vec[i].size(); j++) add(vec[i][0], vec[i][j]);
	}
	int parts = 0;
	for (int i = 0; i < n; i++) {
		if (p[i] == i) parts++;
	}
	parts--;
	for (int i = 0; i < m; i++) if (!arr[i]) parts++;
	cout << parts << endl;
    return 0;
}


#include <vector>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <map>
#include <set>
#include <queue>
#include <string>
#include <cassert>
#include <stack>

#define INFLL 2000000000000000000
#define INF 2000000000
#define MOD 1000000007
#define BASE 77747
#define PI acos(-1.0)
#define MAXLEN 29

typedef std::pair <int, int> pii;
typedef long long ll;
typedef std::vector <ll> vll;

using namespace std;

int n, m, q;
vector <int> vec[200000];
bool arr[200000];
int p[200000];
int sz[200000];

int get(int a) {
	while (p[a] != a) a = p[a];
	return a;
}
void add(int a, int b) {
	a = get(a);
	b = get(b);
	if (a == b) return;
	if (sz[a] < sz[b]) swap(a, b);
	sz[a] += sz[b];
	p[b] = a;
}

int main() {
	cin >> n >> m >> q;
	if (q == 0) {
		cout << n + m - 1 << endl;
		return 0;
	}
	for (int i = 0; i < q; i++) {
		int a, b;
		cin >> a >> b;
		arr[b - 1] = true;
		vec[b - 1].push_back(a - 1);
	}
	for (int i = 0; i < n; i++) {
		sz[i] = 1;
		p[i] = i;
	}
	for (int i = 0; i < m; i++) {
		for (int j = 1; j < vec[i].size(); j++) add(vec[i][0], vec[i][j]);
	}
	int parts = 0;
	for (int i = 0; i < n; i++) {
		if (p[i] == i) parts++;
	}
	parts--;
	for (int i = 0; i < m; i++) if (!arr[i]) parts++;
	cout << parts << endl;
    return 0;
}


#include <vector>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <map>
#include <set>
#include <queue>
#include <string>
#include <cassert>
#include <stack>

#define INFLL 2000000000000000000
#define INF 2000000000
#define MOD 1000000007
#define BASE 77747
#define PI acos(-1.0)
#define MAXLEN 29

typedef std::pair <int, int> pii;
typedef long long ll;
typedef std::vector <ll> vll;

using namespace std;

int n, m, q;
vector <int> vec[200000];
bool arr[200000];
int p[200000];
int sz[200000];

int get(int a) {
	while (p[a] != a) a = p[a];
	return a;
}
void add(int a, int b) {
	a = get(a);
	b = get(b);
	if (sz[a] < sz[b]) swap(a, b);
	sz[a] += sz[b];
	p[b] = a;
}

int main() {
	cin >> n >> m >> q;
	if (q == 0) {
		cout << n + m - 1 << endl;
		return 0;
	}
	for (int i = 0; i < q; i++) {
		int a, b;
		cin >> a >> b;
		arr[b - 1] = true;
		vec[a - 1].push_back(b - 1);
	}
	for (int i = 0; i < n; i++) {
		sz[i] = 1;
		p[i] = i;
	}
	for (int i = 0; i < n; i++) {
		for (int j = 1; j < vec[i].size(); j++) add(vec[i][0], vec[i][j]);
	}
	int parts = 0;
	for (int i = 0; i < n; i++) {
		if (p[i] == i) parts++;
	}
	parts--;
	for (int i = 0; i < m; i++) if (!arr[i]) parts++;
	cout << parts << endl;
    return 0;
}


#include<bits/stdc++.h>

using namespace std;

const int maxi=1e6+2;
const long long mo=1e9+7;
#define pb push_back
vector<int> v[maxi];
pair<pair<int,int>,int> a[maxi];
int n,m,q;
string s;
map<int,int> mp,mp1;
int r[maxi],c[maxi];
int gr[maxi];

void mrdz(int x, int y)
{
    if (v[x].size()<v[y].size()) swap(x,y);

    for (int i:v[y])
    {
        v[x].pb(i);
        gr[i]=x;
    }
    return ;
}
int main()
{
    cin>>n>>m>>q;

   int col=0;
   int row=0;
    for (int i=1;i<=q;i++)
        {
            scanf("%d%d",&r[i],&c[i]);
            a[i]={{c[i],r[i]},i};
            gr[i]=r[i];
            v[r[i]].pb(i);
            mp1[c[i]]++;
            if (mp1[c[i]]==1) col++;
            mp[r[i]]++;
            if (mp[r[i]]==1) row++;
        }
   //if (q==0) return !printf("%d\n",n+m-1);

   sort(a+1,a+n+1);

   int p=row;
   row=n-row;
   col=m-col;

   for (int i=2;i<=n;i++)
    if (a[i].first.first==a[i-1].first.first && gr[a[i].second]!=gr[a[i-1].second])
   {
           p--;
           mrdz(gr[a[i].second],gr[a[i-1].second]);
   }

   cout<<row+col+p-1<<"\n";
     return 0;
}
//Dzanan Musa - "buduci lider NBA ekipe"


#include<bits/stdc++.h>

using namespace std;

const int maxi=1e6+2;
const long long mo=1e9+7;
#define pb push_back
vector<int> v[maxi];
pair<pair<int,int>,int> a[maxi];
int n,m,q;
string s;
map<int,int> mp,mp1;
int r[maxi],c[maxi];
int gr[maxi];

void mrdz(int x, int y)
{
    if (v[x].size()<v[y].size()) swap(x,y);

    for (int i:v[y])
    {
        v[x].pb(i);
        gr[i]=x;
    }
    return ;
}
int main()
{
    cin>>n>>m>>q;

   int col=0;
   int row=0;
    for (int i=1;i<=q;i++)
        {
            scanf("%d%d",&r[i],&c[i]);
            a[i]={{c[i],r[i]},i};
            gr[i]=r[i];
            v[r[i]].pb(i);
            mp1[c[i]]++;
            if (mp1[c[i]]==1) col++;
            mp[r[i]]++;
            if (mp[r[i]]==1) row++;
        }
   //if (q==0) return !printf("%d\n",n+m-1);

   sort(a+1,a+q+1);

   int p=row;
   row=n-row;
   col=m-col;

   for (int i=2;i<=q;i++){
        //cout<<a[i].first.first<<" "<<a[i].first.second<<" "<<a[i].second<<"\n";
    if (a[i].first.first==a[i-1].first.first && gr[a[i].second]!=gr[a[i-1].second])
   {
       //cout<<"DA "<<i<<"\n";
           p--;
           mrdz(gr[a[i].second],gr[a[i-1].second]);
   }

   }
   //cout<<row<<" "<<col<<" "<<p<<"\n";

   cout<<row+col+p-1<<"\n";
     return 0;
}
//Dzanan Musa - "buduci lider NBA ekipe"


#include<bits/stdc++.h>

using namespace std;

const int maxi=1e6+2;
const long long mo=1e9+7;
#define pb push_back
vector<int> v[maxi];
pair<pair<int,int>,int> a[maxi];
int n,m,q;
string s;
map<int,int> mp,mp1;
int r[maxi],c[maxi];
int gr[maxi];

void mrdz(int x, int y)
{
    if (v[x].size()<v[y].size()) swap(x,y);

    for (int i:v[y])
    {
        v[x].pb(i);
        gr[i]=y;
    }
    return ;
}
int main()
{
    cin>>n>>m>>q;

   int col=0;
   int row=0;
    for (int i=1;i<=q;i++)
        {
            scanf("%d%d",&r[i],&c[i]);
            a[i]={{c[i],r[i]},i};
            gr[i]=r[i];
            v[r[i]].pb(i);
            mp1[c[i]]++;
            if (mp1[c[i]]==1) col++;
            mp[r[i]]++;
            if (mp[r[i]]==1) row++;
        }
   if (q==0) return !printf("%d\n",n+m-1);

   sort(a+1,a+n+1);

   int p=row;
   row=n-row;
   col=m-col;

   for (int i=2;i<=n;i++)
    if (a[i].first.first==a[i-1].first.first && gr[a[i].second]!=gr[a[i-1].second])
   {
           p--;
           mrdz(gr[a[i].second],gr[a[i-1].second]);
   }

   cout<<row+col+p-1<<"\n";
     return 0;
}
//Dzanan Musa - "buduci lider NBA ekipe"


// need
#include <iostream>
#include <algorithm>

// data structure
#include <bitset>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include <complex>
//#include <deque>
#include <valarray>

// stream
//#include <istream>
//#include <sstream>
//#include <ostream>
#include <fstream>

// etc
#include <cassert>
#include <cmath>
#include <functional>
#include <iomanip>
#include <chrono>
#include <random>
#include <numeric>

// input
#define INIT std::ios::sync_with_stdio(false);std::cin.tie(0);
#define VAR(type, ...)type __VA_ARGS__;MACRO_VAR_Scan(__VA_ARGS__);
template<typename T> void MACRO_VAR_Scan(T& t) { std::cin >> t; }
template<typename First, typename...Rest>void MACRO_VAR_Scan(First& first, Rest&...rest) { std::cin >> first; MACRO_VAR_Scan(rest...); }
#define VEC_ROW(type, n, ...)std::vector<type> __VA_ARGS__;MACRO_VEC_ROW_Init(n, __VA_ARGS__); for(int i=0; i<n; ++i){MACRO_VEC_ROW_Scan(i, __VA_ARGS__);}
template<typename T> void MACRO_VEC_ROW_Init(int n, T& t) { t.resize(n); }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Init(int n, First& first, Rest&...rest) { first.resize(n); MACRO_VEC_ROW_Init(n, rest...); }
template<typename T> void MACRO_VEC_ROW_Scan(int p, T& t) { std::cin >> t[p]; }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Scan(int p, First& first, Rest&...rest) { std::cin >> first[p]; MACRO_VEC_ROW_Scan(p, rest...); }
#define VEC(type, c, n) std::vector<type> c(n);for(auto& i:c)std::cin>>i;
#define MAT(type, c, m, n) std::vector<std::vector<type>> c(m, std::vector<type>(n));for(auto& r:c)for(auto& i:r)std::cin>>i;

// output
#define OUT(d) std::cout<<(d);
#define FOUT(n, d) std::cout<<std::fixed<<std::setprecision(n)<<(d);
#define SOUT(n, c, d) std::cout<<std::setw(n)<<std::setfill(c)<<(d);
#define SP std::cout<<" ";
#define TAB std::cout<<"\t";
#define BR std::cout<<"\n";
#define SPBR(i, n) std::cout<<(i + 1 == n ? '\n' : ' ');
#define ENDL std::cout<<std::endl;
#define FLUSH std::cout<<std::flush;
#define SHOW(d) {std::cerr << #d << "\t:" << (d) << "\n";}
#define SHOWVECTOR(v) {std::cerr << #v << "\t:";for(const auto& xxx : v){std::cerr << xxx << " ";}std::cerr << "\n";}
#define SHOWVECTOR2(v) {std::cerr << #v << "\t:\n";for(const auto& xxx : v){for(const auto& yyy : xxx){std::cerr << yyy << " ";}std::cerr << "\n";}}
#define SHOWQUEUE(a) {auto tmp(a);std::cerr << #a << "\t:";while(!tmp.empty()){std::cerr << tmp.front() << " ";tmp.pop();}std::cerr << "\n";}

// utility
#define ALL(a) (a).begin(),(a).end()
#define FOR(i, a, b) for(int i=(a);i<(b);++i)
#define RFOR(i, a, b) for(int i=(b)-1;i>=(a);--i)
#define REP(i, n) for(int i=0;i<int(n);++i)
#define RREP(i, n) for(int i=int(n)-1;i>=0;--i)
#define FORLL(i, a, b) for(ll i=ll(a);i<ll(b);++i)
#define RFORLL(i, a, b) for(ll i=ll(b)-1;i>=ll(a);--i)
#define REPLL(i, n) for(ll i=0;i<ll(n);++i)
#define RREPLL(i, n) for(ll i=ll(n)-1;i>=0;--i)
#define IN(a, x, b) (a<=x && x<b)
template<typename T> inline T CHMAX(T& a, const T b) { return a = (a < b) ? b : a; }
template<typename T> inline T CHMIN(T& a, const T b) { return a = (a > b) ? b : a; }
#define EXCEPTION(msg) throw std::string("Exception : " msg " [ in ") + __func__ + " : " + std::to_string(__LINE__) + " lines ]"
#define TRY(cond, msg) try {if (cond) EXCEPTION(msg);}catch (std::string s) {std::cerr << s << std::endl;}
void CHECKTIME(std::function<void()> f) { auto start = std::chrono::system_clock::now(); f(); auto end = std::chrono::system_clock::now(); auto res = std::chrono::duration_cast<std::chrono::nanoseconds>((end - start)).count(); std::cerr << "[Time:" << res << "ns  (" << res / (1.0e9) << "s)]\n"; }

// test
template<class T> std::vector<std::vector<T>> VV(int n, int m, T init = T()) {
	return std::vector<std::vector<T>>(n, std::vector<T>(m, init));
}
template<typename S, typename T>
std::ostream& operator<<(std::ostream& os, std::pair<S, T> p) {
	os << "(" << p.first << ", " << p.second << ")"; return os;
}

// type/const
#define int ll
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using PAIR = std::pair<int, int>;
using PAIRLL = std::pair<ll, ll>;
constexpr int INFINT = 1 << 30;                          // 1.07x10^ 9
constexpr int INFINT_LIM = (1LL << 31) - 1;              // 2.15x10^ 9
constexpr ll INFLL = 1LL << 60;                          // 1.15x10^18
constexpr ll INFLL_LIM = (1LL << 62) - 1 + (1LL << 62);  // 9.22x10^18
constexpr double EPS = 1e-9;
constexpr int MOD = 1000000007;
constexpr double PI = 3.141592653589793238462643383279;

template<class T, size_t N> void FILL(T(&a)[N], const T& val) { for (auto& x : a) x = val; }
template<class ARY, size_t N, size_t M, class T> void FILL(ARY(&a)[N][M], const T& val) { for (auto& b : a) FILL(b, val); }
template<class T> void FILL(std::vector<T>& a, const T& val) { for (auto& x : a) x = val; }
template<class ARY, class T> void FILL(std::vector<std::vector<ARY>>& a, const T& val) { for (auto& b : a) FILL(b, val); }

// ------------>8------------------------------------->8------------

//Union-Find
class UnionFind {
private:
	std::vector<int> parent;
	std::vector<int> height;
	std::vector<int> m_size;

public:
	UnionFind(int size_) : parent(size_), height(size_, 0), m_size(size_, 1) {
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	void init(int size_) {
		parent.resize(size_);
		height.resize(size_, 0);
		m_size.resize(size_, 0);
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	int find(int x) {
		if (parent[x] == x) return x;
		return parent[x] = find(parent[x]);
	}
	void unite(int x, int y) {
		x = find(x);
		y = find(y);
		if (x == y) return;
		int t = size(x) + size(y);
		m_size[x] = m_size[y] = t;
		if (height[x] < height[y]) parent[x] = y;
		else parent[y] = x;
		if (height[x] == height[y]) ++height[x];
	}
	bool same(int x, int y) {
		return find(x) == find(y);
	}
	int size(int x) {
		if (parent[x] == x) return m_size[x];
		return size(parent[x] = find(parent[x]));
	}
};

signed main() {
	INIT;

	VAR(int, n, m, q);
	UnionFind A(n), B(m);
	std::vector<int> C(n, -1), D(m, -1);

	std::set<int> E, F;
	std::function<void(int, int)> add = [&](int y, int x) {
		y = A.find(y); x = B.find(x);
		if (D[x] != -1) {
			int ty = D[x] = A.find(D[x]);
			if (y != ty) {
				if (C[y] != -1) {
					int tx = C[y] = B.find(C[y]);
					if (x != tx) {
						{
							std::set<int> set;
							set.insert(A.find(y));
							set.insert(A.find(ty));
							A.unite(y, ty);
							set.erase(A.find(y));
							E.erase(*set.begin());
						}
						{
							std::set<int> set;
							set.insert(B.find(x));
							set.insert(B.find(tx));
							B.unite(x, tx);
							set.erase(B.find(x));
							F.erase(*set.begin());
						}
					}
					else {
						{
							std::set<int> set;
							set.insert(A.find(y));
							set.insert(A.find(ty));
							A.unite(y, ty);
							set.erase(A.find(y));
							E.erase(*set.begin());
						}
					}
				}
				else {
					{
						std::set<int> set;
						set.insert(A.find(y));
						set.insert(A.find(ty));
						A.unite(y, ty);
						set.erase(A.find(y));
						E.erase(*set.begin());
					}
				}
			}
		}
		else {
			if (C[y] != -1) {
				int tx = C[y] = B.find(C[y]);
				if (x != tx) {
					{
						std::set<int> set;
						set.insert(B.find(x));
						set.insert(B.find(tx));
						B.unite(x, tx);
						set.erase(B.find(x));
						F.erase(*set.begin());
					}
				}
			}
		}
		C[A.find(y)] = B.find(x);
		D[B.find(x)] = A.find(y);
	};

	REP(i, q) {
		VAR(int, r, c);
		--r; --c;
		add(r, c);
	}

	REP(i, n) E.insert(A.find(i));
	REP(i, m) F.insert(B.find(i));

	int ans = 0;
	while (E.size() > 1 || F.size() > 1) {
		[&] {
			for (auto& y : E) {
				for (auto& x : F) {
					int ty = D[x];
					if (ty != -1) ty = A.find(ty);
					int tx = C[y];
					if (tx != -1) tx = B.find(tx);
					if (y != ty && x != tx) {
						++ans;
						add(y, x);
						return;
					}
				}
			}
		}();
	}
	OUT(ans)BR;
	return 0;
}


// need
#include <iostream>
#include <algorithm>

// data structure
#include <bitset>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include <complex>
//#include <deque>
#include <valarray>

// stream
//#include <istream>
//#include <sstream>
//#include <ostream>
#include <fstream>

// etc
#include <cassert>
#include <cmath>
#include <functional>
#include <iomanip>
#include <chrono>
#include <random>
#include <numeric>

// input
#define INIT std::ios::sync_with_stdio(false);std::cin.tie(0);
#define VAR(type, ...)type __VA_ARGS__;MACRO_VAR_Scan(__VA_ARGS__);
template<typename T> void MACRO_VAR_Scan(T& t) { std::cin >> t; }
template<typename First, typename...Rest>void MACRO_VAR_Scan(First& first, Rest&...rest) { std::cin >> first; MACRO_VAR_Scan(rest...); }
#define VEC_ROW(type, n, ...)std::vector<type> __VA_ARGS__;MACRO_VEC_ROW_Init(n, __VA_ARGS__); for(int i=0; i<n; ++i){MACRO_VEC_ROW_Scan(i, __VA_ARGS__);}
template<typename T> void MACRO_VEC_ROW_Init(int n, T& t) { t.resize(n); }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Init(int n, First& first, Rest&...rest) { first.resize(n); MACRO_VEC_ROW_Init(n, rest...); }
template<typename T> void MACRO_VEC_ROW_Scan(int p, T& t) { std::cin >> t[p]; }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Scan(int p, First& first, Rest&...rest) { std::cin >> first[p]; MACRO_VEC_ROW_Scan(p, rest...); }
#define VEC(type, c, n) std::vector<type> c(n);for(auto& i:c)std::cin>>i;
#define MAT(type, c, m, n) std::vector<std::vector<type>> c(m, std::vector<type>(n));for(auto& r:c)for(auto& i:r)std::cin>>i;

// output
#define OUT(d) std::cout<<(d);
#define FOUT(n, d) std::cout<<std::fixed<<std::setprecision(n)<<(d);
#define SOUT(n, c, d) std::cout<<std::setw(n)<<std::setfill(c)<<(d);
#define SP std::cout<<" ";
#define TAB std::cout<<"\t";
#define BR std::cout<<"\n";
#define SPBR(i, n) std::cout<<(i + 1 == n ? '\n' : ' ');
#define ENDL std::cout<<std::endl;
#define FLUSH std::cout<<std::flush;
#define SHOW(d) {std::cerr << #d << "\t:" << (d) << "\n";}
#define SHOWVECTOR(v) {std::cerr << #v << "\t:";for(const auto& xxx : v){std::cerr << xxx << " ";}std::cerr << "\n";}
#define SHOWVECTOR2(v) {std::cerr << #v << "\t:\n";for(const auto& xxx : v){for(const auto& yyy : xxx){std::cerr << yyy << " ";}std::cerr << "\n";}}
#define SHOWQUEUE(a) {auto tmp(a);std::cerr << #a << "\t:";while(!tmp.empty()){std::cerr << tmp.front() << " ";tmp.pop();}std::cerr << "\n";}

// utility
#define ALL(a) (a).begin(),(a).end()
#define FOR(i, a, b) for(int i=(a);i<(b);++i)
#define RFOR(i, a, b) for(int i=(b)-1;i>=(a);--i)
#define REP(i, n) for(int i=0;i<int(n);++i)
#define RREP(i, n) for(int i=int(n)-1;i>=0;--i)
#define FORLL(i, a, b) for(ll i=ll(a);i<ll(b);++i)
#define RFORLL(i, a, b) for(ll i=ll(b)-1;i>=ll(a);--i)
#define REPLL(i, n) for(ll i=0;i<ll(n);++i)
#define RREPLL(i, n) for(ll i=ll(n)-1;i>=0;--i)
#define IN(a, x, b) (a<=x && x<b)
template<typename T> inline T CHMAX(T& a, const T b) { return a = (a < b) ? b : a; }
template<typename T> inline T CHMIN(T& a, const T b) { return a = (a > b) ? b : a; }
#define EXCEPTION(msg) throw std::string("Exception : " msg " [ in ") + __func__ + " : " + std::to_string(__LINE__) + " lines ]"
#define TRY(cond, msg) try {if (cond) EXCEPTION(msg);}catch (std::string s) {std::cerr << s << std::endl;}
void CHECKTIME(std::function<void()> f) { auto start = std::chrono::system_clock::now(); f(); auto end = std::chrono::system_clock::now(); auto res = std::chrono::duration_cast<std::chrono::nanoseconds>((end - start)).count(); std::cerr << "[Time:" << res << "ns  (" << res / (1.0e9) << "s)]\n"; }

// test
template<class T> std::vector<std::vector<T>> VV(int n, int m, T init = T()) {
	return std::vector<std::vector<T>>(n, std::vector<T>(m, init));
}
template<typename S, typename T>
std::ostream& operator<<(std::ostream& os, std::pair<S, T> p) {
	os << "(" << p.first << ", " << p.second << ")"; return os;
}

// type/const
#define int ll
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using PAIR = std::pair<int, int>;
using PAIRLL = std::pair<ll, ll>;
constexpr int INFINT = 1 << 30;                          // 1.07x10^ 9
constexpr int INFINT_LIM = (1LL << 31) - 1;              // 2.15x10^ 9
constexpr ll INFLL = 1LL << 60;                          // 1.15x10^18
constexpr ll INFLL_LIM = (1LL << 62) - 1 + (1LL << 62);  // 9.22x10^18
constexpr double EPS = 1e-9;
constexpr int MOD = 1000000007;
constexpr double PI = 3.141592653589793238462643383279;

template<class T, size_t N> void FILL(T(&a)[N], const T& val) { for (auto& x : a) x = val; }
template<class ARY, size_t N, size_t M, class T> void FILL(ARY(&a)[N][M], const T& val) { for (auto& b : a) FILL(b, val); }
template<class T> void FILL(std::vector<T>& a, const T& val) { for (auto& x : a) x = val; }
template<class ARY, class T> void FILL(std::vector<std::vector<ARY>>& a, const T& val) { for (auto& b : a) FILL(b, val); }

// ------------>8------------------------------------->8------------

//Union-Find
class UnionFind {
private:
	std::vector<int> parent;
	std::vector<int> height;
	std::vector<int> m_size;

public:
	UnionFind(int size_) : parent(size_), height(size_, 0), m_size(size_, 1) {
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	void init(int size_) {
		parent.resize(size_);
		height.resize(size_, 0);
		m_size.resize(size_, 0);
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	int find(int x) {
		if (parent[x] == x) return x;
		return parent[x] = find(parent[x]);
	}
	void unite(int x, int y) {
		x = find(x);
		y = find(y);
		if (x == y) return;
		int t = size(x) + size(y);
		m_size[x] = m_size[y] = t;
		if (height[x] < height[y]) parent[x] = y;
		else parent[y] = x;
		if (height[x] == height[y]) ++height[x];
	}
	bool same(int x, int y) {
		return find(x) == find(y);
	}
	int size(int x) {
		if (parent[x] == x) return m_size[x];
		return size(parent[x] = find(parent[x]));
	}
};

signed main() {
	INIT;

	VAR(int, n, m, q);
	UnionFind A(n), B(m);
	std::vector<int> C(n, -1), D(m, -1);

	std::set<int> E, F;
	REP(i, n) E.insert(i);
	REP(i, m) F.insert(i);
	std::function<void(int, int)> add = [&](int y, int x) {
		y = A.find(y); x = B.find(x);
		if (D[x] != -1) {
			int t = A.find(D[x]);
			if (y != t) {
				A.unite(y, t);
				E.erase(y ^ t ^ A.find(y));
			}
		}
		else {
			D[x] = y;
		}

		y = A.find(y); x = B.find(x);
		if (C[y] != -1) {
			int t = B.find(C[y]);
			if (x != t) {
				B.unite(x, t);
				F.erase(x ^ t ^ B.find(x));
			}
		}
		else {
			C[y] = x;
		}
	};

	REP(i, q) {
		VAR(int, r, c);
		--r; --c;
		add(r, c);
	}

	int ans = 0;
	while (E.size() > 1 || F.size() > 1) {
		for (auto& y : E) {
			for (auto& x : F) {
				int ty = D[B.find(x)];
				if (ty != -1) ty = A.find(ty);
				int tx = C[A.find(y)];
				if (tx != -1) tx = B.find(tx);
				if (y != ty && x != tx) {
					++ans;
					add(y, x);
				}
			}
		}
	}
	OUT(ans)BR;
	return 0;
}


// need
#include <iostream>
#include <algorithm>

// data structure
#include <bitset>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include <complex>
//#include <deque>
#include <valarray>

// stream
//#include <istream>
//#include <sstream>
//#include <ostream>
#include <fstream>

// etc
#include <cassert>
#include <cmath>
#include <functional>
#include <iomanip>
#include <chrono>
#include <random>
#include <numeric>

// input
#define INIT std::ios::sync_with_stdio(false);std::cin.tie(0);
#define VAR(type, ...)type __VA_ARGS__;MACRO_VAR_Scan(__VA_ARGS__);
template<typename T> void MACRO_VAR_Scan(T& t) { std::cin >> t; }
template<typename First, typename...Rest>void MACRO_VAR_Scan(First& first, Rest&...rest) { std::cin >> first; MACRO_VAR_Scan(rest...); }
#define VEC_ROW(type, n, ...)std::vector<type> __VA_ARGS__;MACRO_VEC_ROW_Init(n, __VA_ARGS__); for(int i=0; i<n; ++i){MACRO_VEC_ROW_Scan(i, __VA_ARGS__);}
template<typename T> void MACRO_VEC_ROW_Init(int n, T& t) { t.resize(n); }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Init(int n, First& first, Rest&...rest) { first.resize(n); MACRO_VEC_ROW_Init(n, rest...); }
template<typename T> void MACRO_VEC_ROW_Scan(int p, T& t) { std::cin >> t[p]; }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Scan(int p, First& first, Rest&...rest) { std::cin >> first[p]; MACRO_VEC_ROW_Scan(p, rest...); }
#define VEC(type, c, n) std::vector<type> c(n);for(auto& i:c)std::cin>>i;
#define MAT(type, c, m, n) std::vector<std::vector<type>> c(m, std::vector<type>(n));for(auto& r:c)for(auto& i:r)std::cin>>i;

// output
#define OUT(d) std::cout<<(d);
#define FOUT(n, d) std::cout<<std::fixed<<std::setprecision(n)<<(d);
#define SOUT(n, c, d) std::cout<<std::setw(n)<<std::setfill(c)<<(d);
#define SP std::cout<<" ";
#define TAB std::cout<<"\t";
#define BR std::cout<<"\n";
#define SPBR(i, n) std::cout<<(i + 1 == n ? '\n' : ' ');
#define ENDL std::cout<<std::endl;
#define FLUSH std::cout<<std::flush;
#define SHOW(d) {std::cerr << #d << "\t:" << (d) << "\n";}
#define SHOWVECTOR(v) {std::cerr << #v << "\t:";for(const auto& xxx : v){std::cerr << xxx << " ";}std::cerr << "\n";}
#define SHOWVECTOR2(v) {std::cerr << #v << "\t:\n";for(const auto& xxx : v){for(const auto& yyy : xxx){std::cerr << yyy << " ";}std::cerr << "\n";}}
#define SHOWQUEUE(a) {auto tmp(a);std::cerr << #a << "\t:";while(!tmp.empty()){std::cerr << tmp.front() << " ";tmp.pop();}std::cerr << "\n";}

// utility
#define ALL(a) (a).begin(),(a).end()
#define FOR(i, a, b) for(int i=(a);i<(b);++i)
#define RFOR(i, a, b) for(int i=(b)-1;i>=(a);--i)
#define REP(i, n) for(int i=0;i<int(n);++i)
#define RREP(i, n) for(int i=int(n)-1;i>=0;--i)
#define FORLL(i, a, b) for(ll i=ll(a);i<ll(b);++i)
#define RFORLL(i, a, b) for(ll i=ll(b)-1;i>=ll(a);--i)
#define REPLL(i, n) for(ll i=0;i<ll(n);++i)
#define RREPLL(i, n) for(ll i=ll(n)-1;i>=0;--i)
#define IN(a, x, b) (a<=x && x<b)
template<typename T> inline T CHMAX(T& a, const T b) { return a = (a < b) ? b : a; }
template<typename T> inline T CHMIN(T& a, const T b) { return a = (a > b) ? b : a; }
#define EXCEPTION(msg) throw std::string("Exception : " msg " [ in ") + __func__ + " : " + std::to_string(__LINE__) + " lines ]"
#define TRY(cond, msg) try {if (cond) EXCEPTION(msg);}catch (std::string s) {std::cerr << s << std::endl;}
void CHECKTIME(std::function<void()> f) { auto start = std::chrono::system_clock::now(); f(); auto end = std::chrono::system_clock::now(); auto res = std::chrono::duration_cast<std::chrono::nanoseconds>((end - start)).count(); std::cerr << "[Time:" << res << "ns  (" << res / (1.0e9) << "s)]\n"; }

// test
template<class T> std::vector<std::vector<T>> VV(int n, int m, T init = T()) {
	return std::vector<std::vector<T>>(n, std::vector<T>(m, init));
}
template<typename S, typename T>
std::ostream& operator<<(std::ostream& os, std::pair<S, T> p) {
	os << "(" << p.first << ", " << p.second << ")"; return os;
}

// type/const
#define int ll
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using PAIR = std::pair<int, int>;
using PAIRLL = std::pair<ll, ll>;
constexpr int INFINT = 1 << 30;                          // 1.07x10^ 9
constexpr int INFINT_LIM = (1LL << 31) - 1;              // 2.15x10^ 9
constexpr ll INFLL = 1LL << 60;                          // 1.15x10^18
constexpr ll INFLL_LIM = (1LL << 62) - 1 + (1LL << 62);  // 9.22x10^18
constexpr double EPS = 1e-9;
constexpr int MOD = 1000000007;
constexpr double PI = 3.141592653589793238462643383279;

template<class T, size_t N> void FILL(T(&a)[N], const T& val) { for (auto& x : a) x = val; }
template<class ARY, size_t N, size_t M, class T> void FILL(ARY(&a)[N][M], const T& val) { for (auto& b : a) FILL(b, val); }
template<class T> void FILL(std::vector<T>& a, const T& val) { for (auto& x : a) x = val; }
template<class ARY, class T> void FILL(std::vector<std::vector<ARY>>& a, const T& val) { for (auto& b : a) FILL(b, val); }

// ------------>8------------------------------------->8------------

//Union-Find
class UnionFind {
private:
	std::vector<int> parent;
	std::vector<int> height;
	std::vector<int> m_size;

public:
	UnionFind(int size_) : parent(size_), height(size_, 0), m_size(size_, 1) {
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	void init(int size_) {
		parent.resize(size_);
		height.resize(size_, 0);
		m_size.resize(size_, 0);
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	int find(int x) {
		if (parent[x] == x) return x;
		return parent[x] = find(parent[x]);
	}
	void unite(int x, int y) {
		x = find(x);
		y = find(y);
		if (x == y) return;
		int t = size(x) + size(y);
		m_size[x] = m_size[y] = t;
		if (height[x] < height[y]) parent[x] = y;
		else parent[y] = x;
		if (height[x] == height[y]) ++height[x];
	}
	bool same(int x, int y) {
		return find(x) == find(y);
	}
	int size(int x) {
		if (parent[x] == x) return m_size[x];
		return size(parent[x] = find(parent[x]));
	}
};

signed main() {
	INIT;

	VAR(int, n, m, q);
	UnionFind A(n), B(m);
	std::vector<int> C(n, -1), D(m, -1);

	std::set<int> E, F;
	REP(i, n) E.insert(i);
	REP(i, m) F.insert(i);
	std::function<void(int, int)> add = [&](int y, int x) {
		y = A.find(y); x = B.find(x);
		if (D[x] != -1) {
			int t = A.find(D[x]);
			if (y != t) {
				int yy = A.find(y);
				A.unite(yy, t);
				E.erase(yy ^ t ^ A.find(y));
			}
		}
		else {
			D[x] = y;
		}

		y = A.find(y); x = B.find(x);
		if (C[y] != -1) {
			int t = B.find(C[y]);
			if (x != t) {
				int xx = B.find(x);
				B.unite(xx, t);
				F.erase(xx ^ t ^ B.find(x));
			}
		}
		else {
			C[y] = x;
		}
	};

	REP(i, q) {
		VAR(int, r, c);
		--r; --c;
		add(r, c);
	}

	int ans = 0;
	while (E.size() > 1 || F.size() > 1) {
		for (auto& y : E) {
			for (auto& x : F) {
				int ty = D[B.find(x)];
				if (ty != -1) ty = A.find(ty);
				int tx = C[A.find(y)];
				if (tx != -1) tx = B.find(tx);
				if (y != ty && x != tx) {
					++ans;
					add(y, x);
				}
			}
		}
	}
	OUT(ans)BR;
	return 0;
}


// need
#include <iostream>
#include <algorithm>

// data structure
#include <bitset>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include <complex>
//#include <deque>
#include <valarray>

// stream
//#include <istream>
//#include <sstream>
//#include <ostream>
#include <fstream>

// etc
#include <cassert>
#include <cmath>
#include <functional>
#include <iomanip>
#include <chrono>
#include <random>
#include <numeric>

// input
#define INIT std::ios::sync_with_stdio(false);std::cin.tie(0);
#define VAR(type, ...)type __VA_ARGS__;MACRO_VAR_Scan(__VA_ARGS__);
template<typename T> void MACRO_VAR_Scan(T& t) { std::cin >> t; }
template<typename First, typename...Rest>void MACRO_VAR_Scan(First& first, Rest&...rest) { std::cin >> first; MACRO_VAR_Scan(rest...); }
#define VEC_ROW(type, n, ...)std::vector<type> __VA_ARGS__;MACRO_VEC_ROW_Init(n, __VA_ARGS__); for(int i=0; i<n; ++i){MACRO_VEC_ROW_Scan(i, __VA_ARGS__);}
template<typename T> void MACRO_VEC_ROW_Init(int n, T& t) { t.resize(n); }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Init(int n, First& first, Rest&...rest) { first.resize(n); MACRO_VEC_ROW_Init(n, rest...); }
template<typename T> void MACRO_VEC_ROW_Scan(int p, T& t) { std::cin >> t[p]; }
template<typename First, typename...Rest>void MACRO_VEC_ROW_Scan(int p, First& first, Rest&...rest) { std::cin >> first[p]; MACRO_VEC_ROW_Scan(p, rest...); }
#define VEC(type, c, n) std::vector<type> c(n);for(auto& i:c)std::cin>>i;
#define MAT(type, c, m, n) std::vector<std::vector<type>> c(m, std::vector<type>(n));for(auto& r:c)for(auto& i:r)std::cin>>i;

// output
#define OUT(d) std::cout<<(d);
#define FOUT(n, d) std::cout<<std::fixed<<std::setprecision(n)<<(d);
#define SOUT(n, c, d) std::cout<<std::setw(n)<<std::setfill(c)<<(d);
#define SP std::cout<<" ";
#define TAB std::cout<<"\t";
#define BR std::cout<<"\n";
#define SPBR(i, n) std::cout<<(i + 1 == n ? '\n' : ' ');
#define ENDL std::cout<<std::endl;
#define FLUSH std::cout<<std::flush;
#define SHOW(d) {std::cerr << #d << "\t:" << (d) << "\n";}
#define SHOWVECTOR(v) {std::cerr << #v << "\t:";for(const auto& xxx : v){std::cerr << xxx << " ";}std::cerr << "\n";}
#define SHOWVECTOR2(v) {std::cerr << #v << "\t:\n";for(const auto& xxx : v){for(const auto& yyy : xxx){std::cerr << yyy << " ";}std::cerr << "\n";}}
#define SHOWQUEUE(a) {auto tmp(a);std::cerr << #a << "\t:";while(!tmp.empty()){std::cerr << tmp.front() << " ";tmp.pop();}std::cerr << "\n";}

// utility
#define ALL(a) (a).begin(),(a).end()
#define FOR(i, a, b) for(int i=(a);i<(b);++i)
#define RFOR(i, a, b) for(int i=(b)-1;i>=(a);--i)
#define REP(i, n) for(int i=0;i<int(n);++i)
#define RREP(i, n) for(int i=int(n)-1;i>=0;--i)
#define FORLL(i, a, b) for(ll i=ll(a);i<ll(b);++i)
#define RFORLL(i, a, b) for(ll i=ll(b)-1;i>=ll(a);--i)
#define REPLL(i, n) for(ll i=0;i<ll(n);++i)
#define RREPLL(i, n) for(ll i=ll(n)-1;i>=0;--i)
#define IN(a, x, b) (a<=x && x<b)
template<typename T> inline T CHMAX(T& a, const T b) { return a = (a < b) ? b : a; }
template<typename T> inline T CHMIN(T& a, const T b) { return a = (a > b) ? b : a; }
#define EXCEPTION(msg) throw std::string("Exception : " msg " [ in ") + __func__ + " : " + std::to_string(__LINE__) + " lines ]"
#define TRY(cond, msg) try {if (cond) EXCEPTION(msg);}catch (std::string s) {std::cerr << s << std::endl;}
void CHECKTIME(std::function<void()> f) { auto start = std::chrono::system_clock::now(); f(); auto end = std::chrono::system_clock::now(); auto res = std::chrono::duration_cast<std::chrono::nanoseconds>((end - start)).count(); std::cerr << "[Time:" << res << "ns  (" << res / (1.0e9) << "s)]\n"; }

// test
template<class T> std::vector<std::vector<T>> VV(int n, int m, T init = T()) {
	return std::vector<std::vector<T>>(n, std::vector<T>(m, init));
}
template<typename S, typename T>
std::ostream& operator<<(std::ostream& os, std::pair<S, T> p) {
	os << "(" << p.first << ", " << p.second << ")"; return os;
}

// type/const
#define int ll
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using PAIR = std::pair<int, int>;
using PAIRLL = std::pair<ll, ll>;
constexpr int INFINT = 1 << 30;                          // 1.07x10^ 9
constexpr int INFINT_LIM = (1LL << 31) - 1;              // 2.15x10^ 9
constexpr ll INFLL = 1LL << 60;                          // 1.15x10^18
constexpr ll INFLL_LIM = (1LL << 62) - 1 + (1LL << 62);  // 9.22x10^18
constexpr double EPS = 1e-9;
constexpr int MOD = 1000000007;
constexpr double PI = 3.141592653589793238462643383279;

template<class T, size_t N> void FILL(T(&a)[N], const T& val) { for (auto& x : a) x = val; }
template<class ARY, size_t N, size_t M, class T> void FILL(ARY(&a)[N][M], const T& val) { for (auto& b : a) FILL(b, val); }
template<class T> void FILL(std::vector<T>& a, const T& val) { for (auto& x : a) x = val; }
template<class ARY, class T> void FILL(std::vector<std::vector<ARY>>& a, const T& val) { for (auto& b : a) FILL(b, val); }

// ------------>8------------------------------------->8------------

//Union-Find
class UnionFind {
private:
	std::vector<int> parent;
	std::vector<int> height;
	std::vector<int> m_size;

public:
	UnionFind(int size_) : parent(size_), height(size_, 0), m_size(size_, 1) {
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	void init(int size_) {
		parent.resize(size_);
		height.resize(size_, 0);
		m_size.resize(size_, 0);
		for (int i = 0; i < size_; ++i) parent[i] = i;
	}
	int find(int x) {
		if (parent[x] == x) return x;
		return parent[x] = find(parent[x]);
	}
	void unite(int x, int y) {
		x = find(x);
		y = find(y);
		if (x == y) return;
		int t = size(x) + size(y);
		m_size[x] = m_size[y] = t;
		if (height[x] < height[y]) parent[x] = y;
		else parent[y] = x;
		if (height[x] == height[y]) ++height[x];
	}
	bool same(int x, int y) {
		return find(x) == find(y);
	}
	int size(int x) {
		if (parent[x] == x) return m_size[x];
		return size(parent[x] = find(parent[x]));
	}
};

signed main() {
	INIT;

	VAR(int, n, m, q);
	UnionFind A(n), B(m);
	std::vector<int> C(n, -1), D(m, -1);

	std::set<int> E, F;
	std::function<void(int, int)> add = [&](int y, int x) {
		y = A.find(y); x = B.find(x);
		if (D[x] != -1) {
			int ty = D[x] = A.find(D[x]);
			if (y != ty) {
				if (C[y] != -1) {
					int tx = C[y] = B.find(C[y]);
					if (x != tx) {
						{
							std::set<int> set;
							set.insert(A.find(y));
							set.insert(A.find(ty));
							A.unite(y, ty);
							set.erase(A.find(y));
							E.erase(*set.begin());
						}
						{
							std::set<int> set;
							set.insert(B.find(x));
							set.insert(B.find(tx));
							B.unite(x, tx);
							set.erase(B.find(x));
							F.erase(*set.begin());
						}
					}
					else {
						{
							std::set<int> set;
							set.insert(A.find(y));
							set.insert(A.find(ty));
							A.unite(y, ty);
							set.erase(A.find(y));
							E.erase(*set.begin());
						}
					}
				}
				else {
					{
						std::set<int> set;
						set.insert(A.find(y));
						set.insert(A.find(ty));
						A.unite(y, ty);
						set.erase(A.find(y));
						E.erase(*set.begin());
					}
				}
			}
		}
		else {
			if (C[y] != -1) {
				int tx = C[y] = B.find(C[y]);
				if (x != tx) {
					{
						std::set<int> set;
						set.insert(B.find(x));
						set.insert(B.find(tx));
						B.unite(x, tx);
						set.erase(B.find(x));
						F.erase(*set.begin());
					}
				}
			}
		}
		C[A.find(y)] = B.find(x);
		D[B.find(x)] = A.find(y);
	};

	REP(i, q) {
		VAR(int, r, c);
		--r; --c;
		add(r, c);
	}

	REP(i, n) E.insert(A.find(i));
	REP(i, m) F.insert(B.find(i));

	int ans = 0;
	while (E.size() > 1 || F.size() > 1) {
		[&] {
			for (auto& y : E) {
				for (auto& x : F) {
					int ty = D[x];
					if (ty != -1) ty = A.find(ty);
					int tx = C[y];
					if (tx != -1) tx = B.find(tx);
					if (y != ty && x != tx) {
						++ans;
						add(y, x);
						return;
					}
				}
			}
		}();
	}
	if (D[*F.begin()] == -1 && C[*E.begin()] == -1) ++ans;
	OUT(ans)BR;
	return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define fi freopen("input.txt","r",stdin)
#define fo freopen("output.txt","w",stdout)
#define l(s) int(s.size())
#define pb push_back
int n,m,k;
vector<int> g[400400];
bool used[400400];
void dfs(int v)
{
	used[v]=1;
	for(int j=0;j<g[v].size();j++)
	{
		if(used[g[v][j]])
			continue;
		dfs(g[v][j]);
	}
}
int main()
{
	scanf("%d %d %d",&n,&m,&k);
	while(k--)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		v--;
		u+=n-1;
		g[v].pb(u);
		g[u].pb(v);
	}
	int ans=-1;
	for(int v=0;v<n+m;v++)
	{
		if(used[v])
			continue;
		dfs(v);
		ans++;
	}
	printf("%d\n",ans);
	return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define fi freopen("input.txt","r",stdin)
#define fo freopen("output.txt","w",stdout)
#define l(s) int(s.size())
#define pb push_back
int n,m,k;
vector<int> g[400400];
bool used[400400];
void dfs(int v)
{
	used[v]=1;
	for(int j=0;j<g[v].size();j++)
	{
		if(used[g[v][j]])
			continue;
		dfs(g[v][j]);
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	while(k--)
	{
		int u,v;
		scanf("%d%d",&v,&u);
		v--;
		u+=n-1;
		g[v].pb(u);
		g[u].pb(v);
	}
	int ans=-1;
	for(int v=0;v<n+m;v++)
	{
		if(used[v])
			continue;
		dfs(v);
		ans++;
	}
	printf("%d\n",ans);
	return 0;
}


#include<bits/stdc++.h>
using namespace std;
#define fi freopen("input.txt","r",stdin)
#define fo freopen("output.txt","w",stdout)
#define l(s) int(s.size())
#define pb push_back
int n,m,k;
vector<int> g[400400];
bool used[400400];
void dfs(int v)
{
	used[v]=1;
	for(int j=0;j<g[v].size();j++)
	{
		if(used[g[v][j]])
			continue;
		dfs(g[v][j]);
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	while(k--)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		v--;
		u+=n-1;
		g[v].pb(u);
		g[u].pb(v);
	}
	int ans=-1;
	for(int v=0;v<n+m;v++)
	{
		if(used[v])
			continue;
		dfs(v);
		ans++;
	}
	printf("%d\n",ans);
	return 0;
}