Problem:
MathJax.Hub.Config({ tex2jax: { inlineMath: [["$","$"], ["\\(","\\)"]], processEscapes: true }});

形状データ処理

コンピュータグラフィクスでは、三次元の形状を表現する方法として、ポリゴンモデルが使われます。ポリゴンモデルとは、頂点座標と、それらの頂点のつなぎ方を与えて面を作るモデルです。

一般のポリゴンモデルでは、任意の多角形を扱えますが、今回は三角形からなるポリゴンモデルを考えることにします。任意のポリゴンモデルは三角形を表す面情報の集まりとして表すことができます。

一つの面情報は、３つの頂点を並べて表します。ただし、並び方が異なるだけで同じ３点からなる場合は、同じ面情報を表すことにします。例えば、下図の四面体で、頂点１,２,３を繋いでできる面は、頂点２,３,１や、頂点３,２,１などのように表すこともできます。このように、同じ面情報が複数あると無駄になるので、１つにまとめてしまった方が良いでしょう。

面情報が与えられたとき、重複した面を無くすために消さなければならない面情報の個数を求めるプログラムを作成せよ。

Input

入力は以下の形式で与えられる。

N
p11 p12 p13
p21 p22 p23
:
pN1 pN2 pN3

１行目に、ポリゴンモデルの面情報の数 N (1 ≤ N ≤ 1000) が与えられる。続く N 行に、i 番目の面を作るために使う頂点の番号 pij (1 ≤ pij ≤ 1000) が与えられる。ただし、一つの面について、同じ頂点を２度以上使うことはない（pi1 ≠ pi2 かつ pi2 ≠ pi3 かつ pi1 ≠ pi3 である）。

Output

重複した面を無くすために消さなければならない面情報の個数を１行に出力する。

Sample Input 1

4
1 3 2
1 2 4
1 4 3
2 3 4

Sample Output 1

0

Sample Input 2

6
1 3 2
1 2 4
1 4 3
2 3 4
3 2 1
2 3 1

Sample Output 2

2

入出力例２では、１つ目と５つ目と６つ目の面は頂点1, 3, 2を使って三角形を作っていて、点の順番が異なるだけなので重複している。つまり、重複した面のうち２つの面を消せば重複した面は無くなる。

Solution in Haskell (judge verdict: Accepted)

import Control.Applicative
import Control.Monad
import Control.Monad.ST
-- import qualified Data.ByteString as BS
import Data.Functor
import Data.Function
import Data.Monoid
import Data.Maybe
import Data.List
import qualified Data.Foldable as Foldable
import qualified Data.Set as Set
--import qualified Data.Sequence as Sequence
import Data.List.Split
import Data.Bits
import Data.Char
import Data.Ix
import Data.Ratio
import Data.Ord
import Data.Tuple
import Data.Array
--import Data.Array.Unboxed
--import Data.Array.IArray
import Data.Array.MArray
import Data.Array.IO
import Data.Array.ST
import Data.IORef
import Data.STRef
import GHC.ST
-- import System.IO.Unsafe
 
-- templete
readInt = read :: String -> Int
readInteger = read :: String -> Integer
readDouble = read :: String -> Double
getInt = readLn :: IO Int
getInts = map readInt . words <$> getLine
getInteger = readLn :: IO Integer
getIntegers = map readInteger . words <$> getLine
getDouble = readLn :: IO Double
getDoubles = map readDouble . words <$> getLine
sjoin :: (Show a) => [a] -> String
sjoin = unwords . map show
tjoin :: (Show a, Show b) => (a, b) -> String
tjoin (x, y) = show x ++ (' ' : show y)
cond :: a -> a -> Bool -> a
cond t f c = if c then t else f
apply2 :: (a -> a -> b) -> [a] -> b
apply2 f [x,y] = f x y
apply3 :: (a -> a -> a -> b) -> [a] -> b
apply3 f [x,y,z] = f x y z
apply4 :: (a -> a -> a -> a -> b) -> [a] -> b
apply4 f [x,y,z,w] = f x y z w
fnTuple :: (a -> b, a -> c) -> a -> (b, c)
fnTuple (f,g) a = (f a, g a)
replace :: (Eq a) => a -> a -> [a] -> [a]
replace x y = map (\z -> if z==x then y else z)
binMap :: (a -> a -> b) -> [a] -> [b]
binMap f (x:xs@(y:_)) = f x y : binMap f xs
binMap _ _ = []
splitRec :: Int -> [a] -> [[a]]
splitRec _ [] = []
splitRec n xs = let (y,ys) = splitAt n xs in y : splitRec n ys
infixl 7 `divCeil`
divCeil :: Integral a => a -> a -> a
x `divCeil` y = (x+y-1) `div` y
coverC :: Ord a => (a, a) -> a -> Bool
coverC (l,r) x = l<=x && x<=r
coverH :: Ord a => (a, a) -> a -> Bool
coverH (l,r) x = l<=x && x<r
ibsearch :: (Int -> Bool) -> (Int,Int) -> Int
ibsearch f (ok,ng) = if abs (ok-ng) <= 1 then ok else let mid = (ok + ng) `div` 2 in ibsearch f (if f mid then (mid,ng) else (ok,mid))
-- templete


main = getInt >>= \n -> replicateM n getInts >>= print . (n-) . length . group . sort . map (apply3 (,,) . sort)