-- https://github.com/minoki/my-atcoder-solutions
-- {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}
import Data.Char (isSpace)
-- import Data.Int
import Data.List (unfoldr)
import Control.Monad
import qualified Data.Vector as V
import qualified Data.Vector.Mutable as VM
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Mutable as UM
import qualified Data.ByteString.Char8 as BS
import qualified Data.IntMap as IntMap
import qualified Data.IntSet as IntSet
import Control.Monad.ST

main = do
  n <- readLn
  points <- U.replicateM n $ do
    [x,y] <- unfoldr (BS.readInt . BS.dropWhile isSpace) <$> BS.getLine
    return (x,y)
  let b = 1 + (U.maximum $ U.map fst points)
  let m0 :: IntMap.IntMap IntSet.IntSet
      m0 = U.foldr' (\(x,y) -> IntMap.insertWith IntSet.union x (IntSet.singleton y)) IntMap.empty points
      m1 :: IntMap.IntMap IntSet.IntSet
      m1 = U.foldr' (\(x,y) -> IntMap.insertWith IntSet.union y (IntSet.singleton x)) IntMap.empty points
      m0e :: V.Vector IntSet.IntSet
      m0e = V.fromList $ IntMap.elems m0
      m0a :: V.Vector (Int, IntSet.IntSet)
      m0a = V.fromList $ IntMap.assocs m0
      resultR :: U.Vector Int
      resultS :: V.Vector IntSet.IntSet
      resultN :: U.Vector Int
      (resultR, resultS, resultN) = runST $ do
        root <- U.thaw $ U.enumFromN 0 b
        ss <- VM.replicate b IntSet.empty
        forM_ (IntMap.assocs m0) $ \(k,v) -> VM.write ss k v
        numberOfElements <- UM.replicate b 1
        let getRoot !i = do
              !j <- UM.read root i
              if i == j
                then return i
                else do k <- getRoot j
                        UM.write root i k
                        return k
            unify !i !j = do
              !i' <- getRoot i -- []
              !j' <- getRoot j -- []
              if i' == j'
                then return ()
                else do
                let !k = min i' j'
                UM.write root i' k
                UM.write root j' k
                s1 <- VM.read ss i'
                s2 <- VM.read ss j'
                VM.write ss k $! IntSet.union s1 s2
                n1 <- UM.read numberOfElements i'
                n2 <- UM.read numberOfElements j'
                UM.write numberOfElements k (n1 + n2)
        -- let n = V.length m0e
        forM_ (IntMap.elems m1) $ \t -> do
          let t0:ts = IntSet.toList t
          forM_ ts $ \j ->
            unify t0 j
{-
        forM_ [0..n-1] $ \i -> do
          let s = m0e V.! i
          forM_ [i+1..n-1] $ \j -> do
            let t = m0e V.! j
            unless (IntSet.null $ IntSet.intersection s t) $ do
              unify i j
-}
        liftM3 (,,) (U.freeze root) (V.freeze ss) (U.freeze numberOfElements)
        {-
        foldM (\x -> fmap (+ x)) 0 [ do k <- UM.read root i
                                        if i == k
                                          then IntSet.size <$> VM.read ss i
                                          else return 0
                                   | i <- [0..n-1]]
-}
  --print resultR
  --print resultS
  -- let n' = V.length m0e
  let l = sum [IntSet.size (resultS V.! i) * resultN U.! i | i <- [0..b-1], resultR U.! i == i]
  -- print l
  print $ l - n
  -- print $ result - n
  {-
  let (_,l,s) = IntMap.foldr (\s (i,m,t) -> let !m' = m + i * IntSet.size (IntSet.difference s t) + IntSet.size (IntSet.difference t s)
                                                !u = IntSet.union s t
                                                !i' = i + 1
                                            in (i', m', u)) (0, 0, IntSet.empty) m0
  print $ l
-}


Measured memory_bytes: 41340