import std.stdio, std.conv, std.string, std.array, std.range, std.algorithm, std.container;
import std.math, std.random, std.bigint, std.datetime, std.format;
void main(string[] args){ if(args.length > 1) if(args[1] == "-debug") DEBUG = 1; solve(); }
void log()(){ writeln(""); } void log(T, A ...)(T t, lazy A a){ if(DEBUG) write(t, " "), log(a); } bool DEBUG = 0; 
string read(){ static string[] ss; while(!ss.length) ss = readln.chomp.split; string res = ss[0]; ss.popFront; return res; }
// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- //
void solve(){
	
	int n = read.to!int;
	
	bool[int] xset, yset;
	S[] ps;
	foreach(i; 0 .. n){
		int x = read.to!int - 1;
		if(x !in xset) xset[x] = 1;
		
		int y = read.to!int - 1;
		if(y !in yset) yset[y] = 1;
		
		ps ~= S(x, y);
	}
	
	int[] xs = xset.keys, ys = yset.keys;
	Node[int] xnodes, ynodes;
	
	Node[] nodes;
	foreach(x; xs){
		Node nd = new Node(nodes.length, 1);
		nodes ~= nd;
		xnodes[x] = nd;
	}
	foreach(y; ys){
		Node nd = new Node(nodes.length, 2);
		nodes ~= nd;
		ynodes[y] = nd;
	}
	
	
	Edge[] edges;
	foreach(p; ps){
		edges ~= new Edge(xnodes[p.x], ynodes[p.y], 0);
	}
	
	foreach(ed; edges){
		if(ed.node1.group.id != ed.node2.group.id){
			ed.node1.group.eat(ed.node2.group);
		}
	}
	log("edges:", edges);
	
	long ans;
	foreach(nd; nodes){
		if(nd.group.id != nd.id) continue;
		long xc, yc;
		foreach(k; nd.group.nodes){
			if(k.value == 1) xc += 1; else yc += 1;
		}
		ans += xc * yc;
	}
	
	ans -= n;
	
	ans.writeln;
	
}
/*
縦座標の集合と、横座標の集合で２部グラフとみる
連結成分分解みたいなことをする

元カノの元カレから性病がうつるっていうやつ
連結成分が完全２部グラフになる

元々つながっていた数をひいたら答え
*/

struct S{
	int x, y;
}

// Union-find ※多分使いやすいやつ
class Edge{
	Node node1;
	Node node2;
	long value;
	this(Node node1, Node node2, long value){
		this.node1 = node1, this.node2 = node2;
		this.value = value;
	}
	override string toString(){
		return [node1.id, node2.id, value].to!string;
	}
}

class Node{
	long id;
	long value;
	Group group;
	this(long id, long value){
		this.id = id;
		this.value = value;
		this.group = new Group(this);
	}
	override string toString(){
		return [id, group.id, value].to!string;
	}
}

class Group{
	long value;
	long pendingEdgeCount;
	long id;
	long edgecount;
	Node[] nodes;
	this(Node node){
		this.id = node.id;
		this.nodes = [node];
		this.value = node.value;
	}
	void eat(Group gp){
		if(gp.nodes.length > this.nodes.length) gp.eat(this);
		else{
			foreach(nd; gp.nodes){
				this.nodes ~= nd;
				nd.group = this;
				this.value += nd.value;
			}
			this.edgecount += gp.edgecount;
			this.pendingEdgeCount += gp.pendingEdgeCount;
		}
	}
}
