import java.io._
import java.util.StringTokenizer
import scala.util.control.Breaks.{breakable,break}
import scala.collection.mutable.Queue

object Main extends App {
  val in = new InputReader(System.in)
  val pw = new PrintWriter(System.out)

  val max = 1e5.toInt

  val N = in.next().toInt
  val X = Array.fill[List[Int]](max+1)(Nil)
  val Y = Array.fill[List[Int]](max+1)(Nil)
  for (_ <- 1 to N) {
    val x, y = in.next().toInt
    X(x) = y :: X(x)
    Y(y) = x :: Y(y)
  }

  //
  val dpX = new Array[Boolean](max+1)
  val dpY = new Array[Boolean](max+1)

  var ans = 0L
  for(i <- 1 to max) {
    if(!dpX(i)) {
      var nodex: List[Int] = Nil
      var nodey: List[Int] = Nil
      val q = new Queue[(Int, String)]

      dpX(i) = true
      q.enqueue((i, "x"))
      nodex = i :: nodex

      while(!q.isEmpty) {
        val (n, label) = q.dequeue()

        if(label == "x") {
          for(e <- X(n) if !dpY(e)) {
            dpY(e) = true
            q.enqueue((e, "y"))
            nodey = e :: nodey
          }
        }
        else {
          for(e <- Y(n) if !dpX(e)) {
            dpX(e) = true
            q.enqueue((e, "x"))
            nodex = e :: nodex
          }
        }
      }


      val cntx = nodex.length
      val cnty = nodey.length
      if (cntx >= 2 && cnty >= 2) {
        var edges = 0L

        for (e <- nodex) {
          edges += X(e).length
        }

        ans += cntx.toLong * cnty - edges
      }
    }
  }

  println(ans)
}


class InputReader(stream: InputStream) {
  private val reader = new BufferedReader(new InputStreamReader(stream))
  private var tokenizer: StringTokenizer = new StringTokenizer(reader.readLine())
  def next(): String = {
    while (!tokenizer.hasMoreTokens()) {
      tokenizer = new StringTokenizer(reader.readLine())
    }
    tokenizer.nextToken()
  }
}
