//Let's join Kaede Takagaki Fan Club !!
#pragma GCC optimize("Ofast")
#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;
#define int long long
typedef long long ll;
typedef pair<int,int> P;
typedef pair<int,P> P1;
typedef pair<P,P> P2;
#define pu push
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define eps 1e-7
#define INF 1000000000
#define a first
#define b second
#define rng(i,a,b) for(int i=int(a);i<int(b);i++)
#define rep(i,x) for(int i=0;i<x;i++)
#define repn(i,x) for(int i=1;i<=x;i++)
#define SORT(x) sort(x.begin(),x.end())
#define ERASE(x) x.erase(unique(x.begin(),x.end()),x.end())
#define POSL(x,v) (lower_bound(x.begin(),x.end(),v)-x.begin())
#define POSU(x,v) (upper_bound(x.begin(),x.end(),v)-x.begin())
#define all(x) x.begin(),x.end()
#define si(x) int(x.size())

#ifdef LOCAL
#define dmp(x) cerr<<__LINE__<<" "<<#x<<" "<<x<<endl
#else
#define dmp(x) void(0)
#endif

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

template<class t> using vc=vector<t>;

template<class t,class u>
ostream& operator<<(ostream& os,const pair<t,u>& p){
	return os<<"{"<<p.fi<<","<<p.sc<<"}";
}

template<class t> ostream& operator<<(ostream& os,const vc<t>& v){
	os<<"{";
	for(auto e:v)os<<e<<",";
	return os<<"}";
}

template<class T>
void g(T &a){
	cin >> a;
}
template<class T>
void o(const T &a,bool space=false){
	cout << a << (space?' ':'\n');
}
//ios::sync_with_stdio(false);
const ll mod = 1000000007;//998244353
//mt19937_64 mt(chrono::steady_clock::now().time_since_epoch().count());
template<class T>
void add(T&a,T b){
	a+=b;
	if(a >= mod) a-=mod;
}
ll modpow(ll x,ll n, ll mod){
	ll res=1;
	while(n>0){
		if(n&1) res=res*x%mod;
		x=x*x%mod;
		n>>=1;
	}
	return res;
}
int n, cntv, cnte, deg[400005];
vc<int>edge[400005];
bool vis[400005];
void dfs(int v){
	if(vis[v]) return;
	vis[v] = 1;
	cntv ++;
	cnte += deg[v];
	for(auto at:edge[v]) dfs(at);
}
void solve(){
	cin >> n;
	rep(i, n){
		int a, b; cin >> a >> b;
		edge[a].pb(b);
		edge[b].pb(a);
		deg[a] ++;
		deg[b] ++;
	}
	int ans = 0;
	rep(i, 400005){
		if(vis[i]) continue;
		cntv = cnte = 0; dfs(i);
		if(cntv == cnte/2+1) ans += cntv-1;
		else ans += cntv;
	}
	cout<<ans<<endl;
}
signed main(){
	cin.tie(0);
	ios::sync_with_stdio(0);
	cout<<fixed<<setprecision(20);
	int t; t=1; //cin >> t;
	while(t--) solve();
}

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

namespace kuro {

template<typename Cap>
struct MaxFlow {
 public:
  MaxFlow(int n=0) : _n(0) {
    add_vertices(n);
  }
  int add_vertex() {
    ++_n;
    g.resize(_n);
    return _n-1;
  }
  vector<int> add_vertices(int size) {
    assert(size >= 0);
    vector<int> res(size);
    iota(res.begin(), res.end(), _n);
    _n += size;
    g.resize(_n);
    return res;
  }
  int add_edge(int from, int to, Cap cap) {
    assert(0 <= from && from < _n);
    assert(0 <= to && to < _n);
    assert(0 <= cap);
    int m = (int)pos.size();
    pos.emplace_back(from, (int)g[from].size());
    int from_id = (int)g[from].size();
    int to_id = (int)g[to].size();
    if (from == to) ++to_id;
    g[from].push_back(_edge{to, to_id, cap});
    g[to].push_back(_edge{from, from_id, 0});
    return m;
  }
  Cap flow(int s, int t, Cap flow_limit=numeric_limits<Cap>::max()) {
    assert(0 <= s && s < _n);
    assert(0 <= t && t < _n);
    assert(s != t);
    vector<int> level(_n), iter(_n);
    auto bfs = [&]() {
      fill(level.begin(), level.end(), -1);
      queue<int> que;
      level[s] = 0;
      que.emplace(s);
      while (!que.empty()) {
        int v = que.front(); que.pop();
        for (_edge& e : g[v]) {
          if (e.cap == 0 || level[e.to] >= 0) continue;
          level[e.to] = level[v]+1;
          if (e.to ==  t) return;
          que.emplace(e.to);
        }
      }
    };
    auto dfs = [&](auto self, int v, Cap up) {
      if (v == s) return up;
      Cap res = 0;
      int level_v = level[v];
      for (int& i = iter[v]; i < (int)g[v].size(); ++i) {
        _edge& e = g[v][i];
        if (level_v <= level[e.to]) continue;
        if (g[e.to][e.rev].cap == 0) continue;
        Cap d = self(self, e.to, min(up-res, g[e.to][e.rev].cap));
        if (d <= 0) continue;
        g[v][i].cap += d;
        g[e.to][e.rev].cap -= d;
        res += d;
        if (res == up) return res;
      }
      level[v] = _n;
      return res;
    };
    Cap flow = 0;
    while (flow < flow_limit) {
      bfs();
      if (level[t] == -1) break;
      fill(iter.begin(), iter.end(), 0);
      Cap f = dfs(dfs, t, flow_limit-flow);
      if (!f) break;
      flow += f;
    }
    return flow;
  }
 private:
  int _n;
  struct _edge { int to, rev; Cap cap;};
  vector<pair<int, int>> pos;
  vector<vector<_edge>> g;
};

} // namespace kuro
using namespace kuro;

int main() {
  int n;
  cin >> n;
  vector<int> a(n), b(n);
  set<int> s;
  for (int i = 0; i < n; ++i) {
    cin >> a[i] >> b[i];
    s.insert(a[i]);
    s.insert(b[i]);
  }
  int m = s.size();
  MaxFlow<int> g(n);
  auto nums = g.add_vertices(m);
  int start = g.add_vertex();
  int goal = g.add_vertex();
  map<int, int> mp;
  for (int x : s) {
    int id = mp.size();
    mp[x] = id;
  }
  for (int i = 0; i < n; ++i) {
    g.add_edge(start, i, 1);
  }
  for (int i = 0; i < m; ++i) {
    g.add_edge(nums[i], goal, 1);
  }
  for (int i = 0; i < n; ++i) {
    g.add_edge(i, nums[mp[a[i]]], 1);
    g.add_edge(i, nums[mp[b[i]]], 1);
  }
  int ans = g.flow(start, goal, n);
  cout << ans << '\n';
  return 0;
}