using System;
using System.Collections.Generic;
using System.Linq;
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("6");
WillReturn.Add("-1 -1");
WillReturn.Add("0 1");
WillReturn.Add("0 2");
WillReturn.Add("1 0");
WillReturn.Add("1 2");
WillReturn.Add("2 0");
//3
}
else if (InputPattern == "Input2") {
WillReturn.Add("4");
WillReturn.Add("5 0");
WillReturn.Add("4 1");
WillReturn.Add("-3 -4");
WillReturn.Add("-2 -5");
//4
}
else if (InputPattern == "Input3") {
WillReturn.Add("5");
WillReturn.Add("2 1");
WillReturn.Add("2 -1");
WillReturn.Add("1 0");
WillReturn.Add("3 1");
WillReturn.Add("1 -1");
//1
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
struct XYInfoDef
{
internal int X;
internal int Y;
}
static List<XYInfoDef> mXYList = new List<XYInfoDef>();
static HashSet<int> mXYSet = new HashSet<int>();
// 連結成分のID[ノードID]なDict
static Dictionary<int, int> mTreeDict = new Dictionary<int, int>();
static void Main()
{
List<string> InputList = GetInputList();
int[] wkArr = { };
Action<string> SplitAct = pStr =>
wkArr = pStr.Split(' ').Select(pX => int.Parse(pX)).ToArray();
foreach (string EachStr in InputList.Skip(1)) {
SplitAct(EachStr);
XYInfoDef WillAdd;
WillAdd.X = wkArr[0] + 3000;
WillAdd.Y = wkArr[1] + 3000;
mXYList.Add(WillAdd);
int Hash = GetHash(WillAdd.X, WillAdd.Y);
mXYSet.Add(Hash);
}
ExecDFS();
Console.WriteLine(mTreeDict.Values.Distinct().Count());
}
struct JyoutaiDef
{
internal int CurrX;
internal int CurrY;
}
// 深さ優先探索を行う
static void ExecDFS()
{
for (int I = 0; I <= mXYList.Count - 1; I++) {
int RootHash = GetHash(mXYList[I].X, mXYList[I].Y);
if (mTreeDict.ContainsKey(RootHash)) continue;
var Stk = new Stack<JyoutaiDef>();
JyoutaiDef WillPush;
WillPush.CurrX = mXYList[I].X;
WillPush.CurrY = mXYList[I].Y;
Stk.Push(WillPush);
mTreeDict[RootHash] = RootHash;
while (Stk.Count > 0) {
JyoutaiDef Popped = Stk.Pop();
Action<int, int> PushAct = (pNewX, pNewY) =>
{
int NewHash = GetHash(pNewX, pNewY);
if (mTreeDict.ContainsKey(NewHash)) return;
if (mXYSet.Contains(NewHash) == false) return;
mTreeDict[NewHash] = RootHash;
WillPush.CurrX = pNewX;
WillPush.CurrY = pNewY;
Stk.Push(WillPush);
};
PushAct(Popped.CurrX, Popped.CurrY + 1);
PushAct(Popped.CurrX + 1, Popped.CurrY + 1);
PushAct(Popped.CurrX - 1, Popped.CurrY);
PushAct(Popped.CurrX + 1, Popped.CurrY);
PushAct(Popped.CurrX - 1, Popped.CurrY - 1);
PushAct(Popped.CurrX, Popped.CurrY - 1);
}
}
}
// ハッシュ値を返す
static int GetHash(int pX, int pY)
{
return pX * 10000 + pY;
}
}