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

using ll = long long;
using ull = unsigned long long;
using ld = long double;
#define REP(NAME, NUM) for (int NAME = 0; NAME < (NUM); ++NAME)
#define BREP(NAME, NUM) for (int NAME = (NUM)-1; NAME >= 0; --NAME)
#define ALL(NAME) (NAME).begin(), (NAME).end()

#define cMOD 1000000007ULL
#define cINF ((1ll<<62)-1)
#define cINFINT ((1<<30)-1)



class Graph
{
public:
	explicit Graph( int nodeSize )
		: mNodeSize( nodeSize )
		, mEdgeSize( 0 )
		, mEdge( nodeSize )
		, mDepth( nodeSize )
		, mMark( nodeSize )
		, mMark2( 0 )
		{}
	
	// Edge定義
	struct Edge
	{
		bool operator<( const Edge& other )
		{ return this->cost != other.cost ?
			this->cost < other.cost : this->to < other.to;  }
		int to; int cost; int rev;
	};
	
	// Edge情報を読み取る
	void readEdge_Undirect_NoCost( int m ) { readEdge_( m, false, false ); }
	void readEdge_Direct_NoCost( int m ) { readEdge_( m, true, false ); }
	void readEdge_Undirect_Cost( int m ) { readEdge_( m, false, true ); }
	void readEdge_Direct_Cost( int m ) { readEdge_( m, true, true ); }
	void readEdge_Direct_Flow( int m ) { readEdge_Flow_( m ); }
	// Edge情報のセット
	void setEdge( pair<int, int> e, bool isDirected = false, int cost = 1 )
	{
		mEdge[e.first].push_back( { e.second, cost, -1 } );
		if( !isDirected ) mEdge[e.second].push_back( { e.first, cost, -1 } );
		++mEdgeSize;
	}

private:
	void readEdge_( int m, bool isDirected, bool isCost )
	{
		REP( i, m )
		{
			int a, b, c = 1;
			cin >> a >> b;
			if( isCost ) cin >> c;
			setEdge( { a-1, b-1 }, isDirected, c );
		}
	}
	void readEdge_Flow_( int m )
	{
		REP( i, m )
		{
			int a, b, c;
			cin >> a >> b >> c;
			mEdge[a-1].push_back( { b-1, c, int(mEdge[b-1].size()) } );
			mEdge[b-1].push_back( { a-1, 0, int(mEdge[a-1].size() - 1) } );
			++mEdgeSize;
		}
	}
	
public:
	// 必要なときだけメモリ確保するように
	void assignMark2()
	{
		vector<int> tmp( mNodeSize, cINFINT );
		mMark2.assign( mNodeSize, tmp );
	}
	void setupMark2ForCost()
	{
		REP( i, mNodeSize )
		{
			mMark2[i][i] = 0;
			for( const auto& e : mEdge[i] ) mMark2[i][e.to] = e.cost;
		}
	}


public:
	// MARK構造体
	struct MARK
	{
		int num = 0;
		MARK& operator+=( const MARK& rhs )
		{
			this->num += rhs.num;
			return *this;
		}
	};

public:
	int mNodeSize; // VertexSize
	int mEdgeSize; // EdgeSize
	vector< vector< Edge > > mEdge; // 隣接リスト
	
	// 作業用変数
	vector<int> mDepth /* Used */;
	vector<int> mMark;
	// 必要なときだけメモリ確保するように
	vector< vector<int> > mMark2;
};

pair<ll,ll> dfs_connect_num_( Graph& g, int v )
{
	pair<ll,ll> ret = {0,0};
	if( g.mMark[v] == 1 ) ret.first++;
	else if( g.mMark[v] == 2 ) ret.second++;
	for( const auto& e : g.mEdge[v] )
	{
		if( g.mDepth[e.to] >= 0 ) continue;
		g.mDepth[e.to] = g.mDepth[v] + 1;
		auto a = dfs_connect_num_( g, e.to );
		ret.first += a.first;
		ret.second += a.second;
	}
	return ret;
}
// O( N + M )
pair<ll,ll> dfs_connect_num( Graph& g, int startNode )
{
	g.mDepth[startNode] = 0;
	return dfs_connect_num_( g, startNode );
}

int main()
{
	cin.tie( 0 ); ios::sync_with_stdio( false );
	
	ll n;cin >> n;
	int size = 200000;
	Graph g( size );
	fill( ALL( g.mMark ), -1 );
	fill( ALL( g.mDepth ), -1 );
	REP(i,n)
	{
		int x,y;cin>>x>>y;
		g.setEdge({x-1,y-1+100000});
		g.mMark[x-1] = 1;
		g.mMark[y-1+100000] = 2;
	}

	ll sum = 0;
	REP( i, size ) if( g.mDepth[i] < 0 )
	{
		auto a = dfs_connect_num( g, i );
		sum += a.first * a.second;
	}
	cout << sum - n << endl;
	
	return 0;
}

Measured memory_bytes: 13568