CodeEff/ECCO huggingface dataset:
# Original solution
# UnionFind
import sys
sys.setrecursionlimit(1000000)
class UnionFind:
  def __init__(self, n):
    self.n = [-1]*n
    self.r = [0]*n
    self.siz = n

  def find_root(self, x):
    if self.n[x] < 0:
      return x
    else:
      self.n[x] = self.find_root(self.n[x])
      return self.n[x]

  def unite(self, x, y):
    x = self.find_root(x)
    y = self.find_root(y)
    if x == y:
      return
    elif self.r[x] > self.r[y]:
      self.n[x] += self.n[y]
      self.n[y] = x
    else:
      self.n[y] += self.n[x]
      self.n[x] = y
      if self.r[x] == self.r[y]:
        self.r[y] += 1
    self.siz -= 1

  def root_same(self, x, y):
    return self.find_root(x) == self.find_root(y)

  def count(self, x):
    return -self.n[self.find_root(x)]

  def size(self):
    return self.siz

n,m=list(map(int,input().split()))
u=UnionFind(n)
for _ in range(m):
  a,b=list(map(int,input().split()))
  a-=1
  b-=1
  u.unite(a,b)
print((u.size()-1))

# Optimized solution
class UnionFind:
  def __init__(self, n):
    self.n = [-1]*n
    self.r = [0]*n
    self.co = n

  def find_root(self, x):
    if self.n[x] < 0:
      return x
    else:
      self.n[x] = self.find_root(self.n[x])
      return self.n[x]

  def unite(self, x, y):
    x = self.find_root(x)
    y = self.find_root(y)
    if x == y:
      return
    elif self.r[x] > self.r[y]:
      self.n[x] += self.n[y]
      self.n[y] = x
    else:
      self.n[y] += self.n[x]
      self.n[x] = y
      if self.r[x] == self.r[y]:
        self.r[y] += 1
    self.co -= 1

  def same(self, x, y):
    return self.find_root(x) == self.find_root(y)

  def size(self, x):
    return -self.n[self.find_root(x)]

  def set_count(self):
    return self.co

import sys
input = lambda:sys.stdin.readline().rstrip()

n, m = list(map(int, input().split()))
uf = UnionFind(n)
for _ in range(m):
  a, b = list(map(int, input().split()))
  uf.unite(a-1, b-1)
print((uf.set_count()-1))
# Original solution
import sys
sys.setrecursionlimit(100000)
def dfs(A,N,M,i,j,visited):
    visited[i][j]=1
    rn=[-1,-1,-1,0,0,1,1,1]
    cn=[-1,0,1,-1,1,-1,0,1]
    for s in range(8):
        r=i+rn[s]
        c=j+cn[s]
        if r>=0 and r<N and c>=0 and c<M and not visited[r][c] and A[r][c]:
            dfs(A,N,M,r,c,visited)

from collections import defaultdict
n,m=list(map(int,input().split()))
A=[[0 for i in range(n)] for i in range(n)]
for _ in range(m):
    a,b=list(map(int,input().split()))
    A[a-1][b-1]=1
    A[b-1][a-1]=1
count=0
visited=[[0 for i in range(n)] for i in range(n)]
for i in range(n):
    for j in range(n):
        if A[i][j] and not visited[i][j]:
            count+=1
            dfs(A,n,n,i,j,visited)
        
print(count)


# Optimized solution
from collections import deque
n,m=list(map(int,input().split()))
road=[[] for i in range(n)]
flag=[0]*n
for i in range(m):
    a,b=list(map(int,input().split()))
    road[a-1].append(b-1)
    road[b-1].append(a-1)
count=0
for i in range(n):
    if flag[i]!=0:
        continue
    count+=1
    q=deque([i])
    while q:
        p=q.popleft()
        flag[p]=1
        for z in road[p]:
            if flag[z]==0:
                q.append(z)
print((count-1))
# Original solution
#dt = {} for i in x: dt[i] = dt.get(i,0)+1
import sys;input = sys.stdin.readline
inp,ip = lambda :int(eval(input())),lambda :[int(w) for w in input().split()]
"""
n,k = ip()
x = [inp() for i in range(n)]
"""
import sys
sys.setrecursionlimit(10**6)
def dfs(u):
    global vis,graph
    vis.add(u)
    for i in graph[u]:
        if i not in vis:
            dfs(i)


n,m = ip()
graph = {i:[] for i in range(1,n+1)}
for i in range(m):
    a,b = ip()
    graph[a].append(b)
    graph[b].append(a)
vis = set()
ct = 0
for i in range(1,n+1):
    if i not in vis:
        dfs(i)
        ct += 1
print((ct-1))

# Optimized solution
#dt = {} for i in x: dt[i] = dt.get(i,0)+1
import sys;input = sys.stdin.readline
#import io,os; input = io.BytesIO(os.read(0,os.fstat(0).st_size)).readline #for pypy
inp,ip = lambda :int(eval(input())),lambda :[int(w) for w in input().split()]

class DisjSet: 
    def __init__(self,n): 
        self.rank = [1]*n 
        self.parent = [i for i in range(n)] 

    def find(self,x): 
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x]) 
        return self.parent[x]

    def Union(self,x,y):
        xset = self.find(x) 
        yset = self.find(y)
        if xset == yset: return

        if self.rank[xset] < self.rank[yset]: 
            self.parent[xset] = yset 
        elif self.rank[xset] > self.rank[yset]: 
            self.parent[yset] = xset 
        else: 
            self.parent[yset] = xset 
            self.rank[xset] += 1

n,m = ip()
obj = DisjSet(n)
for i in range(m):
    a,b = ip()
    obj.Union(a-1,b-1)
dt = {}
for i in range(n):
    p = obj.find(i)
    dt[p] = dt.get(p,0)+1
print((len(list(dt.keys()))-1))
# Original solution
n, m = list(map(int, input().split()))
groups = []
flag = [0 for i in range(n)]
for i in range(m):
    a, b = list(map(int, input().split()))
    temp = {a, b}
    if flag[a-1] == 1 or flag[b-1] == 1:
        for j in range(len(groups)):
            if temp & groups[j]:
                groups[j] |= temp
    else:
        groups.append(temp)
    flag[a-1] = 1
    flag[b-1] = 1

print((len(groups)+flag.count(0)-1))

# Optimized solution
class UnionFind:
    def __init__(self, n):
        self.r = [-1 for i in range(n)]
    
    def root(self, x):
        if self.r[x] < 0:
            return x
        self.r[x] = self.root(self.r[x])
        return self.r[x]
    
    def unite(self, x, y):
        x = self.root(x)
        y = self.root(y)
        if x == y:
            return False
        if self.r[x] < self.r[y]:
            temp = x
            x = y
            y = temp
        self.r[x] += self.r[y]
        self.r[y] = x
        return True
    
    def size(self, x):
        return -self.r[self.root(x)]

n, m = list(map(int, input().split()))
UF = UnionFind(n)
for i in range(m):
    a, b = [int(x)-1 for x in input().split()]
    UF.unite(a, b)

ans = 0
for i in UF.r:
    if i < 0:
        ans += 1

print((ans-1))
# Original solution
class UnionFind():
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * n

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x

    def size(self, x):
        return -self.parents[self.find(x)]

    def same(self, x, y):
        return self.find(x) == self.find(y)

    def members(self, x):
        root = self.find(x)
        return [i for i in range(self.n) if self.find(i) == root]

    def roots(self):
        return [i for i, x in enumerate(self.parents) if x < 0]

    def group_count(self):
        return len(self.roots())

    def all_group_members(self):
        return {r: self.members(r) for r in self.roots()}

    def __str__(self):
        return '\n'.join('{}: {}'.format(r, self.members(r)) for r in self.roots())

n, m = list(map(int,input().split()))
uf = UnionFind(n)

for _ in range(m):
    a , b = [int(x)-1 for x in input().split()]
    uf.union(a , b)
    
print((len(uf.all_group_members())-1))

# Optimized solution
class UnionFind():
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * n

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x

    def size(self, x):
        return -self.parents[self.find(x)]

    def same(self, x, y):
        return self.find(x) == self.find(y)

    def members(self, x):
        root = self.find(x)
        return [i for i in range(self.n) if self.find(i) == root]

    def roots(self):
        return [i for i, x in enumerate(self.parents) if x < 0]

    def group_count(self):
        return len(self.roots())

    def all_group_members(self):
        return {r: self.members(r) for r in self.roots()}

    def __str__(self):
        return '\n'.join('{}: {}'.format(r, self.members(r)) for r in self.roots())

n, m = list(map(int,input().split()))
uf = UnionFind(n)

for _ in range(m):
    a , b = [int(x)-1 for x in input().split()]
    uf.union(a , b)
    
print((uf.group_count()-1))
# Original solution
N, M = list(map(int, input().split()))
F = [[] for _ in range(N)]
P = [0] * N
for i in range(M):
    A, B = list(map(int, input().split()))
    F[A-1].append(B-1)
    F[B-1].append(A-1)
c=0
l = []
for i in range(N):
    if P[i] == 0:
        c+=1
        l.append(i)
        while len(l)>0:
            p=l.pop()
            P[p] = c
            for j in F[p]:
                if P[j] == 0:
                    l.append(j)
print((c-1))


# Optimized solution
N, M = list(map(int, input().split()))
Ne = [[] for _ in range(N)]
C = [0] * N
for i in range(M):
    A, B = list(map(int, input().split()))
    Ne[A-1].append(B-1)
    Ne[B-1].append(A-1)
c=0
l = []
for i in range(N):
    if C[i] == 0:
        c+=1
        l.append(i)
        while len(l)>0:
            v=l.pop()
            C[v] = c
            for j in Ne[v]:
                if C[j] == 0:
                    l.append(j)
print((c-1))
# Original solution
import collections

N, M = list(map(int, input().split()))
lst = [0] * N
x = 1
for _ in range(M):
  A, B = list(map(int, input().split()))
  if lst[A-1] == 0 and lst[B-1] == 0:
    lst[A-1] = x
    lst[B-1] = x
    x += 1
  elif lst[A-1] == 0 and lst[B-1] != 0:
    lst[A-1] = lst[B-1]
  elif lst[A-1] != 0 and lst[B-1] == 0:
    lst[B-1] = lst[A-1]
  else:
    y = max(lst[A-1], lst[B-1])
    z = min(lst[A-1], lst[B-1])
    for i in range(N):
      if lst[i] == z:
        lst[i] = y
  
c = collections.Counter(lst)

num_0 = c[0]
if num_0 == 0:  
  print((len(c) - 1))
else:
  print((num_0 + len(c) - 2))
  

# Optimized solution
class UnionFind():
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * n
 
    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]
 
    def unite(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if self.parents[x] > self.parents[y]:
            x, y = y, x
        self.parents[x] += self.parents[y]
        self.parents[y] = x
 
    def size(self, x):
        return -self.parents[self.find(x)]
 
    def same(self, x, y):
        return self.find(x) == self.find(y)
 
    def members(self, x):
        root = self.find(x)
        return [i for i in range(self.n) if self.find(i) == root]
 
    def roots(self):
        return [i for i, x in enumerate(self.parents) if x < 0]
 
    def group_count(self):
        return len(self.roots())
 
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
 
 
 
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
 
n,m=list(map(int,input().split()))
u=UnionFind(n)
for i in range(m):
    a,b=list(map(int,input().split()))
    a-=1
    b-=1
    u.unite(a,b)

print((u.group_count()-1))
# Original solution
n, m = list(map(int, input().split()))
edges = [tuple(map(int, input().split())) for _ in range(m)]

par = [i for i in range(n + 1)]
rank = [1 for _ in range(n + 1)]

def root(x):
    if par[x] == x:
        return x
    else:
        par[x] = root(par[x])
        return par[x]

def same(x, y):
    return root(x) == root(y)

def unite(x, y):
    x = root(x)
    y = root(y)
    if rank[x] < rank[y]:
        par[x] = y
    else:
        par[y] = x
        if rank[x] == rank[y]:
            rank[x] += 1

for a, b in edges:
    unite(a, b)
memo = []
ans = 0
for i in range(1, n + 1):
    if root(i) not in memo:
        memo.append(root(i))
        ans += 1
print((ans - 1))

# Optimized solution
n, m = list(map(int, input().split()))
edges = [tuple(map(int, input().split())) for _ in range(m)]

par = [i for i in range(n + 1)]
rank = [1 for _ in range(n + 1)]

def root(x):
    if par[x] == x:
        return x
    else:
        par[x] = root(par[x])
        return par[x]

def same(x, y):
    return root(x) == root(y)

def unite(x, y):
    x = root(x)
    y = root(y)
    if rank[x] < rank[y]:
        par[x] = y
    else:
        par[y] = x
        if rank[x] == rank[y]:
            rank[x] += 1

for a, b in edges:
    unite(a, b)
memo = [False for _ in range(n + 1)]
ans = 0
for i in range(1, n + 1):
    r = root(i)
    if memo[r] == False:
        memo[r] = True
        ans += 1

print((ans - 1))
# Original solution
from collections import deque

N, M = list(map(int, input().split()))

AB = [[] for _ in range(N)]

for _ in range(M):
    a,b = [int(x)-1 for x in input().split()]
    AB[a].append(b)
    AB[b].append(a)

visited = [0] * N
ans = []
for i in range(N):
    if visited[i]:
        continue
    group = [i]
    d = deque(AB[i])
    while d:
        s = d.popleft()
        if visited[s]:
            continue
        visited[s] = 1
        group.append(s)
        for k in AB[s]:
            d.append(k)
    ans.append(len(group))

print((len(ans)-1))


# Optimized solution
import sys
sys.setrecursionlimit(2 * (10 ** 6))

def main():
    N, M = list(map(int, input().split()))
    AB = [[] for _ in range(N)]

    for _ in range(M):
        a,b = [int(x)-1 for x in input().split()]
        AB[a].append(b)
        AB[b].append(a)

    def dfs(v):
        visited[v] = 1
        for i in AB[v]:
            if visited[i]:
                continue
            dfs(i)

    visited = [0]*N
    cnt = 0
    for i in range(N):
        if visited[i]:
            continue
        dfs(i)
        cnt += 1
    print((cnt-1))

if __name__ == "__main__":
    main()
# Original solution
n,m=list(map(int,input().split()))

#Union-Find木
#par[i]:iの親　deep[i]:iの深さ　size[i]:iの大きさ
par=[i for i in range(n)]
deep=[1]*n
size=[1]*n

#親を見つける
def find(x):
  if par[x]==x:
    return x
  else:
    return find(par[x])

#二つのグループを統合する
def unite(x,y):
  x=find(x)
  y=find(y)
  if x==y:
    return
  if deep[x]<deep[y]:
    par[x]=y
    size[y]+=size[x]
  else:
    par[y]=x
    size[x]+=size[y]
    if deep[x]==deep[y]:
      deep[x]+=1

#xとyが同じグループに属するかどうか
def same(x,y):
  return find(x)==find(y)

#xが属するグループの要素数を返す
def group_count(x):
  return size[find(x)]
  
for i in range(m):
  a,b=list(map(int,input().split()))
  unite(a-1,b-1)
  
from collections import Counter
x=[]
for i in par:
  x.append(find(i))
x=Counter(x)
print((len(x)-1))

  

# Optimized solution
n,m=list(map(int,input().split()))
#Union-Find木
#par[i]:iの親　deep[i]:iの深さ　size[i]:iの大きさ
par=[i for i in range(n)]
deep=[1]*n
size=[1]*n

#親を見つける
def find(x):
  if par[x]==x:
    return x
  else:
    return find(par[x])

#二つのグループを統合する
def unite(x,y):
  x=find(x)
  y=find(y)
  if x==y:
    return
  if deep[x]<deep[y]:
    par[x]=y
    size[y]+=size[x]
  else:
    par[y]=x
    size[x]+=size[y]
    if deep[x]==deep[y]:
      deep[x]+=1

#xとyが同じグループに属するかどうか
def same(x,y):
  return find(x)==find(y)

#xが属するグループの要素数を返す
def group_count(x):
  return size[find(x)]

#連結成分の個数を返す
def count():
  check=set()
  for i in range(n):
    parent=find(i)
    if parent not in check:
      check.add(parent)
  return len(check)

for i in range(m):
  a,b=list(map(int,input().split()))
  unite(a-1,b-1)
s=count()
print((s-1))
# Original solution
from collections import deque
N,M = list(map(int,input().split()))
l = [[] for _ in range(N+1)]
 
for _ in range(M):
  a,b = list(map(int,input().split()))
  l[a].append(b)
  l[b].append(a)
ans = 0
check = [0]*(N+1)
for i in range(1,N+1):
  st = deque([i])
  check[i] = 1
  while st:
    s = st.popleft()
    for t in l[s]:
      if check[t] == 0:
        check[t] = 1
        st.append(t)
  ans = max(ans,N-sum(check))
print(ans)

# Optimized solution
from collections import deque
N,M = list(map(int,input().split()))
l = [[] for _ in range(N+1)]
 
for _ in range(M):
  a,b = list(map(int,input().split()))
  l[a].append(b)
  l[b].append(a)

check = [0]*(N+1)
cnt = 0
for i in range(1,N+1):
  if check[i] == 1:
    continue
  st = deque([i])
  check[i] = 1
  while st:
    s = st.popleft()
    for t in l[s]:
      if check[t] == 0:
        check[t] = 1
        st.append(t)
      continue
  cnt += 1

print((cnt-1))
# Original solution
# -*- coding: utf-8 -*-
import sys 
sys.setrecursionlimit(10**6)
read = sys.stdin.buffer.read
readline = sys.stdin.buffer.readline
readlines = sys.stdin.buffer.readlines

N,M = list(map(int,readline().split()))
road = [[] for _ in range(N+1)]
visit = [0]*(N+1)

for i in range(M):
    x,y = list(map(int,readline().split()))
    road[x].append(y)
    road[y].append(x)
  
def dfs(v,prev):
    for x in road[v]:
        if x == prev:
            continue
        if visit[x] != 0:
            continue
        visit[x] = 1
        dfs(x,v)
    return 

count = 0     
for i in range(1,N+1):
    if visit[i] == 0:
        dfs(i,-1)
        count += 1

print((count-1))

# Optimized solution
# -*- coding: utf-8 -*-
import sys 
sys.setrecursionlimit(10**6)
read = sys.stdin.buffer.read
readline = sys.stdin.buffer.readline
readlines = sys.stdin.buffer.readlines

#Union Find

#xの根を求める
def find(x):
    if par[x] < 0:
        return x
    else:
        par[x] = find(par[x])
        return par[x]
#xとyの属する集合の併合
def union(x,y):
    x = find(x)
    y = find(y)
    if x != y:
        #sizeの大きいほうがx
        if par[x] > par[y]:
            x,y = y,x
        par[x] += par[y] #xにyの個数くっつける 
        par[y] = x #yをxにくっつける
        return 

#xとyが同じ集合に属するかの判定
def same(x,y):
    return find(x) == find(y)

#xが属する集合の個数
def size(x):
    return -par[find(x)]

N,Q = list(map(int,readline().split()))
#0-index
par = [-1]*N
for _ in range(Q):
    x,y = list(map(int,readline().split()))
    x-=1; y-=1
    union(x,y)

ans = set()
for i in range(N):
    ans.add(find(i))
print((len(ans)-1))
# Original solution
# union-find
class UnionFind:
    def __init__(self, n):
        self.prsz = [-1] * n # parent & size
    def find(self, v): # root
        if self.prsz[v] < 0: return v
        self.prsz[v] = self.find(self.prsz[v])
        return self.prsz[v]
    def union(self, v, u): # unite
        v = self.find(v); u = self.find(u)
        if v == u: return False
        if self.prsz[v] > self.prsz[u]: v, u = u, v # size(v) < size(u)
        self.prsz[v] += self.prsz[u]
        self.prsz[u] = v
        return True

n, m = list(map(int, input().split()))
uf = UnionFind(n)
for i in range(m):
    a, b = [int(x)-1 for x in input().split()]
    uf.union(a, b)
ans = set()
for i in range(n):
    ans.add(uf.find(i))
print((len(ans) - 1))


# Optimized solution
# union-find
class UnionFind:
    def __init__(self, n):
        self.prsz = [-1] * n # parent & size
    def find(self, v): # root
        if self.prsz[v] < 0: return v
        self.prsz[v] = self.find(self.prsz[v])
        return self.prsz[v]
    def union(self, v, u): # unite
        v = self.find(v); u = self.find(u)
        if v == u: return False
        if self.prsz[v] > self.prsz[u]: v, u = u, v # size(v) < size(u)
        self.prsz[v] += self.prsz[u]
        self.prsz[u] = v
        return True

n, m = list(map(int, input().split()))
uf = UnionFind(n)
for i in range(m):
    a, b = [int(x)-1 for x in input().split()]
    uf.union(a, b)
ans = -1
for a in uf.prsz:
    if a < 0: ans += 1
print(ans)
# Original solution
def main():
    n, m = list(map(int, input().split()))
    # マイナスの場合は leader であり、同時にサイズ(*-1)を保持する
    uf = [-1] * (n+1)

    def uf_leader(a):
        if uf[a]<0:
            return a
        uf[a] = uf_leader(uf[a])
        return uf[a]
    def uf_unite(a, b):
        ua, ub = uf_leader(a), uf_leader(b)
        if ua==ub:
            return False
        if uf[ua] > uf[ub]:
            a, b = b, a
        uf[ua] += uf[ub]
        uf[ub] = ua
        return True
    def uf_leaders():
        return set([uf_leader(v) for v in range(1,n+1)])

    # print(uf[1:])
    for _ in range(m):
        a, b = list(map(int, input().split()))
        uf_unite(a, b)
        # print(uf[1:])

    # print(uf_leaders())
    
    ans = len(uf_leaders())-1
    print(ans)

main()

# Optimized solution
def main():
    n, m = list(map(int, input().split()))
    # マイナスの場合は leader であり、同時にサイズ(*-1)を保持する
    uf = [-1] * (n+1)

    def uf_leader(a):
        if uf[a]<0:
            return a
        uf[a] = uf_leader(uf[a])
        return uf[a]
    def uf_unite(a, b):
        ua, ub = uf_leader(a), uf_leader(b)
        if ua==ub:
            return False
        if uf[ua] > uf[ub]:
            ua, ub = ub, ua
        uf[ua] += uf[ub]
        uf[ub] = ua
        return True
    def uf_leaders():
        return set([uf_leader(v) for v in range(1,n+1)])

    # print(uf[1:])
    for _ in range(m):
        a, b = list(map(int, input().split()))
        uf_unite(a, b)
        # print(uf[1:])

    # print(uf_leaders())
    
    ans = len(uf_leaders())-1
    print(ans)

main()
# Original solution
def main():
    n, m = list(map(int, input().split()))
    # マイナスの場合は leader であり、同時にサイズ(*-1)を保持する
    uf = [-1] * (n+1)

    def uf_leader(a):
        if uf[a]<0:
            return a
        uf[a] = uf_leader(uf[a])
        return uf[a]
    def uf_unite(a, b):
        ua, ub = uf_leader(a), uf_leader(b)
        if ua==ub:
            return False
        if uf[ua] > uf[ub]:
            ua, ub = ub, ua
        uf[ua] += uf[ub]
        uf[ub] = ua
        return True
    def uf_leaders():
        return set([uf_leader(v) for v in range(1,n+1)])

    # print(uf[1:])
    for _ in range(m):
        a, b = list(map(int, input().split()))
        uf_unite(a, b)
        # print(uf[1:])

    # print(uf_leaders())
    
    ans = len(uf_leaders())-1
    print(ans)

main()

# Optimized solution
def main():
    n, m = list(map(int, input().split()))
    # マイナスの場合は leader であり、同時にサイズ(*-1)を保持する
    uf = [-1] * (n+1)

    def uf_leader(a):
        if uf[a]<0:
            return a
        uf[a] = uf_leader(uf[a])
        return uf[a]
    def uf_unite(a, b):
        ua, ub = uf_leader(a), uf_leader(b)
        if ua==ub:
            return False
        if uf[ua] > uf[ub]:
            ua, ub = ub, ua
        uf[ua] += uf[ub]
        uf[ub] = ua
        return True
    def uf_leaders():
        return [v for v in range(1,n+1) if uf[v]<0]

    # print(uf[1:])
    for _ in range(m):
        a, b = list(map(int, input().split()))
        uf_unite(a, b)
        # print(uf[1:])

    # print(uf_leaders())
    
    ans = len(uf_leaders())-1
    print(ans)

main()
# Original solution
N,M = list(map(int,input().split()))
ans = N-1
d = []
for m in range(M):
  A,B = list(map(int,input().split()))
  for i,di in enumerate(d):
    if set([A,B]).issubset(di):
      d[i] = di|set([A,B])
      break
  else:
    d.append(set([A,B]))
    ans -= 1
print(ans)

# Optimized solution
def f(x):
  while p[x]>0: x = p[x]
  return x
N,M = list(map(int,input().split()))
p = [-1]*N
for _ in range(M):
  A,B = [f(int(x)-1) for x in input().split()]
  if A==B: continue
  elif A<B: A,B=B,A
  p[A] += p[B]
  p[B] = A
print((sum(i<0 for i in p)-1))