{-# OPTIONS_GHC -O2 #-}
import Control.Applicative
import Data.List
import qualified Data.ByteString.Char8 as BC
import qualified Data.ByteString as BS
import Data.Maybe (fromJust)

data Node = Node { getxy :: Int, getn :: Int } deriving (Eq, Show)
type Edge = (Node, Node)

emptyN :: Node
emptyN = Node (-1) (-1)

getNode :: Int -> Edge -> Node
getNode xy (n1,n2)
    | (getxy n1) == xy = n1
    | otherwise        = n2

next :: [Edge] -> Node -> Node -> [(Node, Node)]
next es b c = zip (repeat c) $ map (getNode (getxy b)) . filter (\(n1,n2) -> (n1 == c && n2 /= b) || (n2 == c && n1 /= b)) $ es

nulltest :: ([a] -> Maybe b) -> [a] -> Maybe b
nulltest f l
    | null l    = Nothing
    | otherwise = f l

dfs :: [Edge] -> Node -> [Node]
dfs es n = flip unfoldr [(emptyN, n)] $ nulltest (\((b,c):xs) -> Just (c, (next es b c) ++ xs))

getg :: [Edge] -> [Node] -> [[Node]]
getg es [] = []
getg es ns = g : getg es (g \\ ns)
    where g = dfs es $ head ns


f :: [Edge] -> Int
f es = (subtract (length es)).sum.(map fm).(getg es) $ ns
    where 
      ns = nub.concat.(\(a,b) -> [a,b]).unzip $ es
      fm = (\(x,y) -> (length x) * (length y)).(partition $ (== 0).getxy)
      
main :: IO()
main = BC.getContents >>= print . f . map ((\[x,y] -> (Node 0 x ,Node 1 y)) . map (fst . fromJust . BC.readInt) . BC.words) . tail . BC.lines
