The "BerCorp" company has got n employees. These employees can use m approved official languages for the formal correspondence. The languages are numbered with integers from 1 to m. For each employee we have the list of languages, which he knows. This list could be empty, i. e. an employee may know no official languages. But the employees are willing to learn any number of official languages, as long as the company pays their lessons. A study course in one language for one employee costs 1 berdollar.

Find the minimum sum of money the company needs to spend so as any employee could correspond to any other one (their correspondence can be indirect, i. e. other employees can help out translating).


-----Input-----

The first line contains two integers n and m (2 ≤ n, m ≤ 100) — the number of employees and the number of languages.

Then n lines follow — each employee's language list. At the beginning of the i-th line is integer k_{i} (0 ≤ k_{i} ≤ m) — the number of languages the i-th employee knows. Next, the i-th line contains k_{i} integers — a_{ij} (1 ≤ a_{ij} ≤ m) — the identifiers of languages the i-th employee knows. It is guaranteed that all the identifiers in one list are distinct. Note that an employee may know zero languages.

The numbers in the lines are separated by single spaces.


-----Output-----

Print a single integer — the minimum amount of money to pay so that in the end every employee could write a letter to every other one (other employees can help out translating).


-----Examples-----
Input
5 5
1 2
2 2 3
2 3 4
2 4 5
1 5

Output
0

Input
8 7
0
3 1 2 3
1 1
2 5 4
2 6 7
1 3
2 7 4
1 1

Output
2

Input
2 2
1 2
0

Output
1



-----Note-----

In the second sample the employee 1 can learn language 2, and employee 8 can learn language 4.

In the third sample employee 2 must learn language 2.

Difficulty: MEDIUM

Solutions:
(n, k) = map(int, input().split())
ans = 0
temp = []
for i in range(n):
	li = set(list(map(int, input().split()))[1:])
	if len(li) < 1:
		ans += 1
	elif not temp:
		temp.append(set(li))
	else:
		cnt = False
		for j in temp:
			if j.intersection(li):
				cnt = True
				for k in li:
					j.add(k)
		if not cnt:
			temp.append(set(li))
ans2 = []
ln = len(temp)
while True:
	cnt = False
	(founded, zero) = (False, False)
	if zero:
		break
	for i in range(ln):
		for j in range(ln):
			if temp[i].intersection(temp[j]) and i != j:
				cnt = True
				for k in temp[j]:
					temp[i].add(k)
				del temp[j]
				ln -= 1
				if ln == 0:
					zero = True
				founded = True
			if founded:
				break
		if founded:
			break
	if not cnt:
		break
ln2 = len(temp) - 1
if ln2 >= 0:
	print(ln2 + ans)
else:
	print(ans)


(n, m) = map(int, input().split())
adj = [[] for i in range(n + 1)]
vis = [0] * (n + 1)

def dfs(v):
	vis[v] = 1
	for i in range(len(adj[v])):
		if vis[adj[v][i]] == 0:
			dfs(adj[v][i])
langs = [[] for i in range(n + 1)]
for i in range(1, n + 1):
	lang = list(map(int, input().split()))
	if len(lang) == 1:
		continue
	for j in range(1, len(lang)):
		langs[i].append(lang[j])
for i in range(1, n + 1):
	for j in range(i + 1, n + 1):
		if len(set(langs[i]).intersection(set(langs[j]))) != 0:
			adj[i].append(j)
			adj[j].append(i)
cc = 0
flag = 0
for i in range(1, n + 1):
	if len(langs[i]) == 0:
		flag += 1
	if vis[i] == 0:
		cc += 1
		dfs(i)
if flag == n:
	cc += 1
print(cc - 1)


from collections import defaultdict

class Graph:

	def __init__(self):
		self.graph = defaultdict(list)

	def addEdge(self, u, v):
		self.graph[u].append(v)

	def DFSUtil(self, v, visited):
		visited[v] = True
		for i in self.graph[v]:
			if visited[i] == False:
				self.DFSUtil(i, visited)

	def DFS(self, Dic):
		V = len(self.graph)
		visited = Dic
		cnt1 = 0
		for i in visited.keys():
			if visited[i] == False:
				cnt1 += 1
				self.DFSUtil(i, visited)
		return max(cnt1 - 1, 0)
g = Graph()
(n, m) = map(int, input().split())
cnt = 0
Dic = {}
for i in range(n):
	A = [int(x) for x in input().split()]
	length = A[0]
	if length == 0:
		cnt += 1
		continue
	A = A[1:]
	for j in range(length):
		Dic[A[j]] = False
		for k in range(length):
			g.addEdge(A[j], A[k])
cnt += g.DFS(Dic)
print(cnt)


def union(x, y, par):
	x1 = x
	while par[x1] >= 0:
		x1 = par[x1]
	y1 = y
	while par[y1] >= 0:
		y1 = par[y1]
	if x1 == y1:
		return
	if par[x1] < par[y1]:
		setpar = x1
		par[y1] = x1
	else:
		setpar = y1
		par[x1] = y1
	while par[x] != x1 and x != x1:
		temp = par[x]
		par[x] = setpar
		x = temp
	while par[y] != y1 and y != y1:
		temp = par[y]
		par[y] = setpar
		y = temp
(n, m) = map(int, input().split())
par = [-1 for i in range(m)]
val = [False for i in range(m)]
cnt1 = 0
for i in range(n):
	a = list(map(int, input().split()))
	if a[0] == 0:
		cnt1 += 1
		continue
	for j in range(len(a)):
		a[j] -= 1
	for j in range(1, len(a)):
		val[a[j]] = True
	for j in range(2, len(a)):
		union(a[1], a[j], par)
cnt2 = 0
for i in range(m):
	if par[i] == -1 and val[i]:
		cnt2 += 1
print(max(cnt2 - 1, 0) + cnt1)


(n, m) = map(int, input().split())
graph = {i: set() for i in range(1, 1 + n)}
for i in range(101, 101 + m):
	graph[i] = set()
zc = 0
for i in range(1, 1 + n):
	a = list(map(int, input().split()))
	if a.pop(0) == 0:
		zc += 1
	for j in a:
		graph[i].add(100 + j)
		graph[100 + j].add(i)

def dfs(v):
	unvis[v] = False
	for i in graph[v]:
		for j in graph[i]:
			if unvis[j]:
				dfs(j)
if zc == n:
	print(n)
else:
	cc = 0
	unvis = [True] * (n + 1)
	for k in range(1, 1 + n):
		if unvis[k]:
			dfs(k)
			cc += 1
	print(cc - 1)


(n, m) = map(int, input().split())
cnt0 = 0
l = []
for _ in range(n):
	l1 = []
	a = set([int(x) for x in input().split()][1:])
	if not a:
		cnt0 += 1
		continue
	for x in l:
		if x & a:
			a |= x
		else:
			l1.append(x)
	l1.append(a)
	l = l1
print(cnt0 + max(0, len(l) - 1))


def union(s, n):
	i = 0
	while i < n:
		j = i + 1
		while j < n:
			if s[i] & s[j]:
				s[i] = s[i] | s[j]
				del s[j]
				n -= 1
			else:
				j += 1
		i += 1
	return (s, n)
(n, m) = [int(i) for i in input().split()]
languages = []
ans = 0
for i in range(n):
	tmp = set([int(j) for j in input().split()[1:]])
	if tmp:
		languages.append(tmp)
	else:
		n -= 1
		ans += 1
(languages, n) = union(languages, n)
(languages, n) = union(languages, n)
if languages:
	ans += n - 1
print(ans)


from collections import defaultdict

def dfs(root, visited, graph):
	visited[root] = 1
	for i in graph[root]:
		if visited[i] == 0:
			dfs(i, visited, graph)
(n, m) = map(int, input().split())
graph = defaultdict(list)
l = []
lingo = [0 for i in range(m + 1)]
flag = 0
for i in range(n):
	l.append(list(map(int, input().split())))
	for j in range(1, 1 + l[i][0]):
		flag = 1
		if lingo[l[i][j]] != 0:
			graph[lingo[l[i][j]]].append(i + 1)
			graph[i + 1].append(lingo[l[i][j]])
		else:
			lingo[l[i][j]] = i + 1
visited = [0 for i in range(n + 1)]
c = 0
for i in range(1, n + 1):
	if visited[i] == 0:
		dfs(i, visited, graph)
		c += 1
if flag == 0:
	c += 1
print(c - 1)


import sys
input = lambda : sys.stdin.readline().strip('\r\n')
(n, m) = map(int, input().split())
people = {}
graph = {i: [] for i in range(1, n + 1)}
for i in range(1, n + 1):
	a = list(map(int, input().split()))
	people[i] = a[1:]
for person in people:
	for lang in people[person]:
		for other_person in people:
			if lang in people[other_person] and other_person != person:
				graph[person].append(other_person)
if all((len(people[i]) == 0 for i in people)):
	print(n)
	quit()

def dfs(node):
	if vis[node]:
		return
	vis[node] = True
	for child in graph[node]:
		if not vis[child]:
			dfs(child)
vis = [False] * (n + 1)
connected = 0
for i in range(1, n + 1):
	if not vis[i]:
		connected += 1
		dfs(i)
print(connected - 1)


(n, m) = map(int, input().split())
l = []
for i in range(n):
	t = list(map(int, input().split()))
	s = set()
	if t[0] == 0:
		continue
	for j in range(1, t[0] + 1):
		s.add(t[j])
	l.append(s)
ans = 0
(s1, s) = (set(), set())
for i in range(len(l)):
	s = l[i]
	f = 0
	for j in range(len(l)):
		if i == j:
			continue
		if len(l[j].intersection(s)) != 0:
			s = s.union(l[j])
	if len(s.intersection(s1)) == 0:
		ans += 1
	s1 = s1.union(s)
print(max(0, ans - 1) + n - len(l))


I = lambda : list(map(int, input().split()))
(n, m) = I()
lr = 0
langs = []
for i in range(n):
	L = I()
	if L[0] == 0:
		lr += 1
	else:
		langs.append(set(L[1:]))
check = [1] * len(langs)
for i in range(len(langs)):
	if check[i]:
		for j in range(len(langs)):
			if i != j and check[j] and (len(langs[i] & langs[j]) > 0):
				check[j] = 0
				langs[i] = langs[i].union(langs[j])
if sum(check):
	print(sum(check) + lr - 1)
else:
	print(lr)


(n, m) = map(int, input().split())
adj = [[] for i in range(n)]
s = [set(list(map(int, input().split()))[1:]) for _ in range(n)]
if sum(map(len, s)) == 0:
	print(n)
	exit(0)
for i in range(m + 1):
	for k1 in range(n):
		for k2 in range(n):
			if i in s[k1] and i in s[k2]:
				adj[k1].append(k2)
				adj[k2].append(k1)
comp = []
visited = [0] * n

def dfs(v):
	if visited[v]:
		return
	comp[-1].append(v)
	visited[v] = 1
	for u in adj[v]:
		dfs(u)
count = 0
for v in range(n):
	if not visited[v]:
		comp.append([])
		dfs(v)
		count += 1
print(count - 1)


(n, m) = map(int, input().split())
l = []
count = 0
for i in range(n):
	k = list(map(int, input().split()))
	if k[0] > 0:
		p = k[0]
		del k[0]
		k1 = []
		for i in k:
			ans = False
			j = 0
			for o in l:
				if i in o:
					ans = True
					break
				j += 1
			if ans:
				if j not in k1:
					k1.append(j)
		k1.sort()
		k1.reverse()
		for o in k1:
			k = k + l[o]
			del l[o]
		l.append(k)
	else:
		count += 1
print(max(len(l) - 1, 0) + count)


(n, m) = map(int, input().split())
l = []
for _ in range(n):
	t = list(map(int, input().split()))
	l.append(t)
adj = []
for i in range(1 + n + m):
	adj.append([])
for i in range(n):
	el = l[i]
	pl = i + 1
	for a in range(1, el[0] + 1):
		s = pl
		e = el[a] + n
		adj[s].append(e)
		adj[e].append(s)
visited = [0] * (1 + n + m)

def bfs(node):
	if visited[node]:
		return
	visited[node] = 1
	for i in adj[node]:
		bfs(i)
p = 0
t = 0
for i in range(1, 1 + n + m):
	if visited[i] == 0:
		if i > n and len(adj[i]) == 0:
			pass
		elif i <= n and len(adj[i]) == 0:
			t += 1
		else:
			bfs(i)
			p += 1
print(max(p - 1, 0) + t)


from sys import stdin, stdout
input = stdin.readline

def main():
	(n, m) = map(int, input().split())
	sets = list(range(n + 1 + m))
	sizes = [1] * (n + 1 + m)

	def get(a):
		if sets[a] != a:
			sets[a] = get(sets[a])
		return sets[a]

	def union(a, b):
		a = get(a)
		b = get(b)
		if a == b:
			return
		if sizes[a] > sizes[b]:
			(a, b) = (b, a)
		sets[a] = b
		sizes[b] += sizes[a]
	flags = [1] * n
	for i in range(n):
		nums = list(map(int, input().split()))
		for j in range(1, nums[0] + 1):
			union(i, nums[j] - 1 + n)
		flags[i] = nums[0]
	ans = 0
	s = set()
	for i in range(n):
		if flags[i] == 0:
			ans += 1
			continue
		s.add(get(i))
	print(max(0, len(s) - 1) + ans)
main()


def fact(x):
	if x == 0:
		return 1
	else:
		return x * fact(x - 1)

def lower_bound(li, num):
	answer = -1
	start = 0
	end = len(li) - 1
	while start <= end:
		middle = (end + start) // 2
		if li[middle] >= num:
			answer = middle
			end = middle - 1
		else:
			start = middle + 1
	return answer

def upper_bound(li, num):
	answer = -1
	start = 0
	end = len(li) - 1
	while start <= end:
		middle = (end + start) // 2
		if li[middle] <= num:
			answer = middle
			start = middle + 1
		else:
			end = middle - 1
	return answer

def abs(x):
	return x if x >= 0 else -x

def binary_search(li, val, lb, ub):
	ans = 0
	while lb <= ub:
		mid = (lb + ub) // 2
		if li[mid] > val:
			ub = mid - 1
		elif val > li[mid]:
			lb = mid + 1
		else:
			ans = 1
			break
	return ans

def kadane(x):
	sum_so_far = 0
	current_sum = 0
	for i in x:
		current_sum += i
		if current_sum < 0:
			current_sum = 0
		else:
			sum_so_far = mpos(sum_so_far, current_sum)
	return sum_so_far

def pref(li):
	pref_sum = [0]
	for i in li:
		pref_sum.append(pref_sum[-1] + i)
	return pref_sum

def graph(n, m):
	adj = dict()
	for i in range(1, n + 1):
		adj.setdefault(i, 0)
	for i in range(m):
		(a, b) = map(int, input().split())
		adj[a] += 1
		adj[b] += 1
	return adj
import sys
import threading
from math import *
sys.setrecursionlimit(300000)
threading.stack_size(10 ** 8)
po = int(log(10 ** 18)) + 2
li = []
for i in range(po):
	li.append(3 ** i)
li = li[::-1]
s = sum(li)
for _ in range(1):
	(n, m) = map(int, input().split())
	lang = {}
	emp = {}
	for i in range(1, m + 1):
		lang.setdefault(i, [])
	for i in range(1, n + 1):
		emp.setdefault(i, [])
	for i in range(1, n + 1):
		li = list(map(int, input().split()))
		for j in range(1, len(li)):
			lang[li[j]] += [i]
	f = 0
	for i in lang.values():
		if len(i) != 0:
			f = 1
			break
	if f:
		for (k, v) in lang.items():
			for i in range(1, len(v)):
				emp[v[0]] += [v[i]]
				emp[v[i]] += [v[0]]
		vis = [0] * n
		cnt = 0

		def dfs(n):
			vis[n - 1] = 1
			for i in emp[n]:
				if vis[i - 1] == 0:
					dfs(i)
		for i in range(n):
			if vis[i] == 0:
				cnt += 1
				dfs(i + 1)
		print(cnt - 1)
	else:
		print(n)


def main():
	(n, m) = list(map(int, input().split()))
	f = [1] * n
	lang = [set(map(int, input().split()[1:])) for _ in range(n)]
	for (i, langi) in enumerate(lang):
		for (j, langj) in enumerate(lang[:i]):
			if f[j] and langj.intersection(langi):
				f[j] = 0
				langi.update(langj)
	print(sum(f) - any(lang))

def __starting_point():
	main()
__starting_point()


(n, m) = map(int, input().split())
w = 0
r = []
for i in range(n):
	(k, *l) = map(int, input().split())
	if k == 0:
		w += 1
	else:
		r.append(set(l))
q = []
while q != r:
	q = list(r)
	for i in range(len(r)):
		for j in range(len(r)):
			if r[i] & r[j]:
				r[i] = r[i] | r[j]
q = []
for i in r:
	if i not in q:
		q.append(i)
print(len(q) - 1 * int(len(q) != 0) + w)


def recursive_dfs(graph, start, path=[]):
	path = path + [start]
	for node in graph[start]:
		if not node in path:
			path = recursive_dfs(graph, node, path)
	return path

def connected(x, y):
	if set(x).intersection(set(y)) != set():
		return True
	return False
vertices = []
each = {}
adj = {}
num0 = 0
(a, b) = list(map(int, input().split(' ')))
for i in range(a):
	x = list(map(int, input().split(' ')))
	adj[i] = []
	if x[0] == 0:
		num0 += 1
	else:
		x = x[1:]
		each[i] = x
		vertices.append(i)
		for j in vertices:
			if connected(each[j], each[i]):
				adj[j].append(i)
				adj[i].append(j)
marked = [False] * a
num = 0
for i in vertices:
	if not marked[i]:
		for j in recursive_dfs(adj, i):
			marked[j] = True
		num += 1
if num == 0:
	print(num0)
else:
	print(num + num0 - 1)


import math, sys, bisect, heapq
from collections import defaultdict, Counter, deque
from itertools import groupby, accumulate
int1 = lambda x: int(x) - 1
input = iter(sys.stdin.buffer.read().decode().splitlines()).__next__
ilele = lambda : map(int, input().split())
alele = lambda : list(map(int, input().split()))
ilelec = lambda : map(int1, input().split())
alelec = lambda : list(map(int1, input().split()))

def list2d(a, b, c):
	return [[c] * b for i in range(a)]

def list3d(a, b, c, d):
	return [[[d] * c for j in range(b)] for i in range(a)]

def Y(c):
	print(['NO', 'YES'][c])

def y(c):
	print(['no', 'yes'][c])

def Yy(c):
	print(['No', 'Yes'][c])
(n, m) = ilele()
G = defaultdict(list)
ans = 0
for i in range(n):
	A = alele()
	if A[0] == 0:
		ans += 1
	for j in A[1:]:
		G[j].append(i + 1)
H = defaultdict(list)
x = list(G.keys())
for i in range(len(x)):
	for j in range(i + 1, len(x)):
		if len(G[x[i]]) + len(G[x[j]]) != len(set(G[x[i]] + G[x[j]])):
			a = x[i]
			b = x[j]
			H[a].append(b)
			H[b].append(a)
mark = {}

def dfs(node):
	mark[node] = True
	for j in H.get(node, []):
		if mark.get(j, False) == False:
			dfs(j)
ff = 0
for i in x:
	if mark.get(i, 0) == False:
		ff = 1
		ans += 1
		dfs(i)
if ff:
	ans -= 1
print(ans)


def dfs(v):
	used[v] = 1
	for i in emp[v]:
		for j in lang[i]:
			if used[j] == 0:
				dfs(j)
R = lambda : list(map(int, input().split(' ')))
ans = []
(n, m) = R()
(used, lang, emp) = ([0 for i in range(n + 1)], [[] for i in range(m + 1)], [[] for i in range(n + 1)])
for i in range(1, n + 1):
	a = R()
	for j in range(1, a[0] + 1):
		lang[a[j]].append(i)
		emp[i].append(a[j])
for i in range(1, n + 1):
	if used[i] == 0:
		dfs(i)
		if len(emp[i]) != 0:
			ans.append(1)
		else:
			ans.append(0)
print(len(ans) - 1 if sum(ans) != 0 else len(ans))


def find(x):
	if x != par[x]:
		par[x] = find(par[x])
	return par[x]
(n, m) = map(int, input().split())
l1 = [0] * n
par = [i for i in range(m + 1)]
for i in range(n):
	l = list(map(int, input().split()))
	if l[0] != 0:
		l1[i] = find(par[l[1]])
		for j in range(2, len(l)):
			par[find(l[j])] = find(par[l[1]])
s = set()
ans = 0
for i in l1:
	k = find(i)
	s.add(k)
	if k == 0:
		ans += 1
if 0 in s:
	if len(s) > 1:
		print(len(s) - 2 + ans)
	else:
		print(n)
else:
	print(len(s) - 1)


(n, m) = map(int, input().split())
languages = [[list(map(int, input().split()))[1:]] + [False] for i in range(n)]
c = 0
w = 0
for i in range(len(languages)):
	g = languages[i - w][0]
	if not g:
		c += 1
		languages.pop(i - w)
		w += 1

def connected(languages, lang):
	lang[1] = True
	for x in range(len(languages)):
		i = languages[x]
		if i[1]:
			continue
		p = False
		for y in i[0]:
			if y in lang[0]:
				p = True
				break
		if not i[1] and p:
			languages[x][1] = True
			lang[0].extend(i[0])
			lang[0] = list(dict.fromkeys(lang[0]))
			connected(languages, lang)
if languages:
	c -= 1
	for x in languages:
		if not x[1]:
			c += 1
			connected(languages, x)
	print(c)
else:
	print(c)


(n, m) = [int(x) for x in input().split()]
l = [[]] * n
for i in range(n):
	l[i] = set((int(x) for x in input().split()[1:]))
while True:
	for i in range(len(l)):
		for j in range(i):
			if l[i] & l[j]:
				l[j] |= l[i]
				l = l[:i] + l[i + 1:]
				break
		else:
			continue
		break
	else:
		break
ans = len(l)
for s in l:
	if s:
		ans -= 1
		break
print(ans)


MOD = 1000000007
MOD2 = 998244353
ii = lambda : int(input())
si = lambda : input()
dgl = lambda : list(map(int, input()))
f = lambda : map(int, input().split())
il = lambda : list(map(int, input().split()))
ls = lambda : list(input())
(n, m) = f()
l = []
c = 0
for i in range(n):
	(a, *b) = f()
	b = set(b)
	if len(b):
		l.append(b)
	else:
		c += 1
bl = [0] * len(l)
for i in range(len(l)):
	if bl[i] == 0:
		x = l[i]
		for j in range(i, len(l)):
			for k in range(i, len(l)):
				if bl[k] == 0 and len(x.intersection(l[k])):
					x = x.union(l[k])
					bl[k] = i + 1
print(max(0, len(set(bl)) - 1) + c)


rd = lambda : list(map(int, input().split()))

def root(x):
	if f[x] != x:
		f[x] = root(f[x])
	return f[x]
(n, m) = rd()
N = list(range(n))
f = list(N)
lang = [0] * n
for i in N:
	lang[i] = set(rd()[1:])
for i in N:
	for j in N[:i]:
		if j == root(j) and lang[j].intersection(lang[i]):
			f[j] = i
			lang[i] = lang[i].union(lang[j])
print(sum((1 for i in N if i == root(i))) - (sum(map(len, lang)) > 0))


(n, m) = map(int, input().split())
degrees = [0 for _ in range(m)]
parent = [i for i in range(m)]
size = [1 for _ in range(m)]

def find(child):
	if parent[child] == child:
		return child
	parent[child] = find(parent[child])
	return parent[child]

def union(child_a, child_b):
	root_a = find(child_a)
	root_b = find(child_b)
	if root_a == root_b:
		return
	if size[root_a] > size[root_b]:
		tmp = root_a
		root_a = root_b
		root_b = tmp
	size[root_b] += size[root_a]
	parent[root_a] = root_b
cost = 0
for _ in range(n):
	emp = list(map(int, input().split()))
	if emp[0] < 1:
		cost += 1
	for l in emp[1:]:
		degrees[l - 1] += 1
	if emp[0] < 2:
		continue
	for l in emp[2:]:
		union(emp[1] - 1, l - 1)
p = 0
for (i, d) in enumerate(degrees):
	if d > 0:
		p = i
		break
for i in range(m):
	if degrees[i] > 0 and find(i) != find(p):
		union(p, i)
		if degrees[0] + degrees[i] > 0:
			cost += 1
		else:
			cost += 2
print(cost)


(n, m) = [int(x) for x in input().split()]
lang = dict()
for i in range(1, m + 1):
	lang[i] = []
g = dict()
color = dict()
for i in range(1, n + 1):
	g[i] = []
	color[i] = 0
for i in range(1, n + 1):
	inp = [int(x) for x in input().split()]
	for j in range(inp[0]):
		lang[inp[j + 1]].append(i)
for x in lang.values():
	for i in range(len(x) - 1):
		g[x[i]].append(x[i + 1])
		g[x[i + 1]].append(x[i])
all_zero_lang = True
for i in range(1, m + 1):
	if lang[i]:
		all_zero_lang = False
		break
if all_zero_lang:
	print(n)
else:
	cntr = -1
	q = [i for i in range(1, n + 1)]
	while q:
		u = q.pop(0)
		if color[u] == 0:
			cntr += 1
			color[u] = 1
			queue = [u]
			while queue:
				w = queue.pop(0)
				for x in g[w]:
					if color[x] == 0:
						color[x] = 1
						queue.append(x)
	print(cntr)


(n, m) = map(int, input().split())
l = []
s = []
r = 0
for i in range(n):
	l = list(map(int, input().split()))
	if l[0] == 0:
		r += 1
	else:
		s.append(set(l[1:]))
i = 0
while i < len(s):
	for j in s[i + 1:]:
		if s[i] & j:
			s[i] |= j
			s.remove(j)
			break
	else:
		i += 1
print(max(0, len(s) - 1 + r, r))


import os
import sys
from io import BytesIO, IOBase
input = sys.stdin.readline

def inp():
	return int(input())

def inlt():
	return list(map(int, input().split()))

def insr():
	s = input()
	return list(s[:len(s) - 1])

def invr():
	return map(int, input().split())

def gcd(a, b):
	if b == 0:
		return a
	else:
		return gcd(b, a % b)

def seive(n):
	primes = [True] * (n + 1)
	for i in range(2, n):
		if not primes[i]:
			continue
		j = 2 * i
		while j <= n:
			primes[j] = False
			j += i
	return primes

def factors(n):
	factors = []
	x = 2
	while x * x <= n:
		while n % x == 0:
			factors.append(x)
			n //= x
		if n > 1:
			factors.append(x)
	return factors

def main():
	try:
		(n, m) = invr()
		s = [0] * n
		adj = [[] for i in range(n)]
		for i in range(n):
			l = inlt()
			for k in l[1:]:
				s[i] = s[i] | 1 << k
		if sum(s) == 0:
			print(n)
			return
		for i in range(n):
			for j in range(n):
				if s[i] & s[j] != 0:
					adj[i].append(j)
		visited = [0] * n

		def dfs(v):
			if visited[v]:
				return
			else:
				visited[v] = 1
				for u in adj[v]:
					dfs(u)
		count = 0
		for i in range(n):
			if not visited[i]:
				dfs(i)
				count += 1
		print(count - 1)
	except Exception as e:
		print(e)
BUFSIZE = 8192

class FastIO(IOBase):
	newlines = 0

	def __init__(self, file):
		self._fd = file.fileno()
		self.buffer = BytesIO()
		self.writable = 'x' in file.mode or 'r' not in file.mode
		self.write = self.buffer.write if self.writable else None

	def read(self):
		while True:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			if not b:
				break
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines = 0
		return self.buffer.read()

	def readline(self):
		while self.newlines == 0:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			self.newlines = b.count(b'\n') + (not b)
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines -= 1
		return self.buffer.readline()

	def flush(self):
		if self.writable:
			os.write(self._fd, self.buffer.getvalue())
			(self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

	def __init__(self, file):
		self.buffer = FastIO(file)
		self.flush = self.buffer.flush
		self.writable = self.buffer.writable
		self.write = lambda s: self.buffer.write(s.encode('ascii'))
		self.read = lambda : self.buffer.read().decode('ascii')
		self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')
main()


from sys import stdin
input = stdin.readline
(n, m) = map(int, input().split())
l = []
cnt = 0
for _ in range(n):
	a = set(list(map(int, input().split()))[1:])
	if len(a) == 0:
		cnt += 1
		continue
	li = []
	for s in l:
		if a & s:
			a |= s
		else:
			li.append(s)
	li.append(a)
	l = li
print(cnt + max(0, len(l) - 1))


p = [i for i in range(110)]
rank = [0 for i in range(110)]

def findSet(i):
	nonlocal p
	if p[i] == i:
		return i
	p[i] = findSet(p[i])
	return p[i]

def isSameSet(i, j):
	return findSet(i) == findSet(j)

def unionSet(i, j):
	nonlocal p, rank
	if isSameSet(i, j):
		return
	x = findSet(i)
	y = findSet(j)
	if rank[x] > rank[y]:
		p[y] = x
	else:
		p[x] = y
		if rank[x] == rank[y]:
			rank[y] += 1
(n, m) = tuple((int(i) for i in input().split()))
langs = [set() for i in range(n)]
allzero = True
for i in range(n):
	A = list((int(i) for i in input().split()))
	if A[0] != 0:
		allzero = False
	for j in range(len(A)):
		if j > 0:
			langs[i].add(A[j])
if not allzero:
	for i in range(n):
		for j in range(i + 1, n):
			if len(langs[i] & langs[j]) >= 1:
				unionSet(i, j)
	roots = set()
	for i in range(n):
		roots.add(findSet(i))
	print(len(roots) - 1)
else:
	print(n)


(n, nLangs) = map(int, input().split())
a = [set()]
atLeastOne = 0
for i in range(1, n + 1):
	a.append(set(list(map(int, input().split()))[1:]))
	if a[-1]:
		atLeastOne = 1
g = [[] for _ in range(n + 1)]
for p1 in range(1, n):
	for p2 in range(p1 + 1, n + 1):
		if a[p1].intersection(a[p2]):
			g[p1].append(p2)
			g[p2].append(p1)

def dfs(v):
	visited[v] = True
	for to in g[v]:
		if not visited[to]:
			dfs(to)
visited = [False] * (n + 1)
ret = 0
for p in range(1, n + 1):
	if not visited[p]:
		dfs(p)
		ret += 1
print(ret - atLeastOne)


(n, m) = map(int, input().split(' '))
parent = {}
for i in range(1, n + 1):
	parent['e' + str(i)] = 'e' + str(i)
for j in range(1, m + 1):
	parent['l' + str(j)] = 'l' + str(j)

def find(i):
	if parent[i] == i:
		return i
	parent[i] = find(parent[i])
	return parent[i]

def union(i, j):
	parent[find(i)] = find(j)
for i in range(1, n + 1):
	line = input()
	if line != '0':
		for j in line.split(' ')[1:]:
			union('e' + str(i), 'l' + j)
for i in range(1, n + 1):
	find('e' + str(i))
for j in range(1, m + 1):
	find('l' + str(j))
m = {}
for i in range(1, n + 1):
	x = 'e' + str(i)
	p = parent[x]
	if p in m:
		m[p] += 1
	else:
		m[p] = 0
numofzerokeys = 0
for i in range(1, n + 1):
	if 'e' + str(i) in m and m['e' + str(i)] == 0:
		numofzerokeys += 1
if numofzerokeys == n:
	print(n)
else:
	print(len(m) - 1)


(n, m) = [int(i) for i in input().split()]
d = []
r = 0
for i in range(n):
	a = [int(i) for i in input().split()]
	if a[0] == 0:
		r += 1
	else:
		d += [set(a[1:])]
i = 0
while i < len(d):
	for j in d[i + 1:]:
		if d[i] & j:
			d[i] |= j
			d.remove(j)
			break
	else:
		i += 1
print(max(0, len(d) - 1) + r)


import bisect, sys
from collections import deque, namedtuple
N = 300
par = [i for i in range(N)]

def find(i):
	root = i
	while root != par[root]:
		root = par[root]
	while i != par[i]:
		newp = par[i]
		par[i] = root
		i = newp
	return root

def merge(a, b):
	a = find(a)
	b = find(b)
	par[b] = a

def main():
	(n, m) = map(int, input().split())
	(check, ans) = (0, -1)
	for _ in range(n):
		l = [int(i) for i in input().split()][1:]
		check = max(check, len(l))
		for i in l:
			merge(_, n + i)
	if not check:
		print(n)
		return
	for i in range(n):
		if i == par[i]:
			ans += 1
	print(ans)

def __starting_point():
	main()
__starting_point()


def dfs(v):
	visited[v] = True
	for u in graph[v]:
		if not visited[u]:
			dfs(u)
(n, c) = map(int, input().split())
graph = [[] for i in range(n)]
lang = [list(map(int, input().split()))[1:] for _ in range(n)]
if not sum(list(map(lambda lst: len(lst), lang))):
	print(n)
	exit()
for lang_number in range(1, c + 1):
	knows_language = []
	for i in range(n):
		if lang_number in lang[i]:
			knows_language.append(i)
	for i in knows_language:
		graph[i] += knows_language
visited = [False] * n
comp_cnt = 0
cnt_odd = 0
for i in range(n):
	cnt_odd += len(graph[i]) % 2
	if not visited[i]:
		dfs(i)
		comp_cnt += 1
print(comp_cnt - 1)


(n, m) = map(int, input().split())
lang = [set(list(map(int, input().split()))[1:]) for _ in range(n)]
g = {}
count = 0
for (i, e) in enumerate(lang):
	if len(e) != 0:
		g[i] = []
		for (j, o) in enumerate(lang):
			if i != j and len(o) != 0:
				if len(e & o) != 0:
					g[i].append(j)
	else:
		count += 1
if len(g) != 0:
	ma = []
	for i in range(n):
		if i in g:
			stack = [i]
			visited = [i]
			while len(stack) != 0:
				r = stack.pop()
				for j in g[r]:
					if j not in visited:
						visited.append(j)
						stack.append(j)
			visited = sorted(visited)
			if visited not in ma:
				ma.append(visited)
	count += len(ma) - 1
print(count)


from sys import stdin

def find(par, a):
	if par[a] is not a:
		par[a] = find(par, par[a])
	return par[a]

def merge(par, rnk, a, b):
	a = find(par, a)
	b = find(par, b)
	if a == b:
		return
	if rnk[a] < rnk[b]:
		par[a] = b
	else:
		par[b] = a
		if rnk[a] == rnk[b]:
			rnk[b] += 1

def solve(tc):
	(n, m) = map(int, stdin.readline().split())
	employee = [[False for i in range(m + 1)] for j in range(n)]
	illiteracy = [False for i in range(n)]
	cntIlliteracy = 0
	for i in range(n):
		seq = list(map(int, stdin.readline().split()))
		if seq[0] == 0:
			illiteracy[i] = True
			cntIlliteracy += 1
		for j in range(seq[0]):
			employee[i][seq[j + 1]] = True
	comp = list(range(n))
	rnk = [0 for i in range(n)]
	for i in range(n):
		if illiteracy[i]:
			continue
		for j in range(i + 1, n):
			if illiteracy[j]:
				continue
			ok = False
			for k in range(m + 1):
				if employee[i][k] and employee[j][k]:
					ok = True
					break
			if ok:
				merge(comp, rnk, i, j)
	group = set()
	for i in range(n):
		if not illiteracy[i]:
			group.add(find(comp, i))
	print((len(group) - 1 if len(group) > 1 else 0) + cntIlliteracy)
tcs = 1
for tc in range(tcs):
	solve(tc)


(n, m) = [int(x) for x in input().split()]
lnum = []
language = []
can = 0
for i in range(n):
	s = [int(x) for x in input().split()]
	lnum.append(s[0])
	language.append(s[1:len(s)])
	if len(s) > 1:
		can = 1
stat = [0] * n

def dfs(k):
	stat[k] = 1
	res = 1
	for i in range(n):
		if stat[i] == 0:
			st = 0
			for i1 in language[i]:
				for i2 in language[k]:
					if i1 == i2:
						st = 1
						res += dfs(i)
						break
				if st == 1:
					break
	return res
comp = 0
for k in range(n):
	if stat[k] == 0:
		comp += 1
		d = dfs(k)
if can == 0:
	print(n)
else:
	print(comp - 1)


def contains(lst, grph):
	for el in lst:
		if len(el & grph) > 0:
			return True
	return False
res = 0
(n, m) = map(int, input().split(' '))
graphs = []
for _ in range(n):
	line = input().split(' ')[1:]
	if len(line) == 0:
		res += 1
		continue
	flag = False
	for lang in line:
		for graph in graphs:
			if int(lang) in graph:
				flag = True
				graph |= set(map(int, line))
	if not flag:
		graphs.append(set(map(int, line)))
new_graphs = []
for graph in graphs:
	flag = False
	for group in new_graphs:
		if contains(group, graph):
			flag = True
			group.append(graph)
			break
	if not flag:
		new_graphs.append([graph])
print(res + (len(new_graphs) - 1 if len(new_graphs) > 0 else 0))


def fastio():
	import sys
	from io import StringIO
	from atexit import register
	global input
	sys.stdin = StringIO(sys.stdin.read())
	input = lambda : sys.stdin.readline().rstrip('\r\n')
	sys.stdout = StringIO()
	register(lambda : sys.__stdout__.write(sys.stdout.getvalue()))
fastio()
MOD = 10 ** 9 + 7
I = lambda : list(map(int, input().split()))
(n, m) = I()
p = [0] * 200
for i in range(200):
	p[i] = i

def find(u):
	if p[u] == u:
		return u
	else:
		p[u] = find(p[u])
	return p[u]

def join(u, v):
	x = find(u)
	y = find(v)
	if x != y:
		p[x] = y
st = set()
count = 0
for i in range(n):
	t = I()
	(k, l) = (t[0], t[1:])
	if k == 0:
		count += 1
	for i in l:
		join(l[0], i)
		st.add(i)
ans = 0
v = [0] * 200
s = set()
for i in st:
	s.add(find(i))
print(max(0, len(s) - 1) + count)


def dfs(s):
	if visited[s]:
		return
	visited[s] = 1
	for j in adj[s]:
		dfs(j)
(n, m) = map(int, input().split())
T = lambda : [[] for i in range(n + m)]
adj = T()
ans = -1
s = []
r = 0
for i in range(n):
	l = list(map(lambda x: int(x) + n - 1, input().split()))
	adj[i] = l[1:]
	for j in adj[i]:
		adj[j] += [i]
visited = [0] * (n + m)
if adj == T():
	ans += 1
for i in range(n):
	if not visited[i]:
		dfs(i)
		ans += 1
print(ans)


def Find(i):
	if par[i] != i:
		par[i] = Find(par[i])
	return par[i]

def Union(a, b):
	a = Find(a)
	b = Find(b)
	if a == b:
		return False
	par[a] = b
	return True
(n, m) = map(int, input().split())
par = [i for i in range(n)]
adj = []
tot = 0
ans = n - 1
for i in range(n):
	adj.append(set(map(int, input().split()[1:])))
	tot += len(adj[i])
	for j in range(i):
		if adj[i] & adj[j]:
			ans -= Union(i, j)
if not tot:
	ans += 1
print(ans)


(n, m) = map(int, input().split())

def root(arr, x):
	while arr[x] != x:
		x = arr[arr[x]]
	return x

def find(arr, a, b):
	r_a = root(arr, a)
	r_b = root(arr, b)
	if r_a == r_b:
		return True
	return False

def union(a, b, arr, sz):
	r_a = root(arr, a)
	r_b = root(arr, b)
	if sz[r_a] > sz[r_b]:
		arr[r_b] = r_a
		sz[r_a] += sz[r_b]
	else:
		arr[r_a] = r_b
		sz[r_b] += sz[r_a]
arr = [{} for i in range(n + 1)]
cs = 0
for i in range(n):
	ls = list(map(int, input().split()))
	s = set(ls[1:])
	if len(s) == 0:
		cs += 1
	arr[i + 1] = s
ls = [i for i in range(n + 1)]
sz = [1 for i in range(n + 1)]
for i in range(1, n + 1):
	for j in range(i + 1, n + 1):
		if not find(ls, i, j):
			xs = arr[i] & arr[j]
			if len(xs) != 0:
				union(i, j, ls, sz)
				arr[i] = arr[i] | arr[j]
				arr[j] = arr[i] | arr[j]
ss = set()
temp = [0]
d = dict()
for i in range(1, len(ls)):
	xs = root(ls, i)
	if xs not in d:
		d[xs] = 0
	d[xs] += 1
cc = co = 0
for i in d.keys():
	if d[i] > 1:
		cc += 1
	else:
		co += 1
if cs == n:
	print(n)
else:
	print(co + cc - 1)


def dfs(i, v, vi, lan, emp, bn):
	if bn:
		vi[i] = 1
		for j in lan[i]:
			if not v[j]:
				dfs(j, v, vi, lan, emp, (bn + 1) % 2)
	else:
		v[i] = 1
		for j in emp[i]:
			if not vi[j]:
				dfs(j, v, vi, lan, emp, (bn + 1) % 2)

def main():
	emp = {}
	lan = {}
	num0 = ans = 0
	(n, m) = list(map(int, input().split()))
	for i in range(m):
		lan[i] = set()
	for i in range(n):
		emp[i] = set()
		k = list(map(int, input().split()))
		for j in range(1, k[0] + 1):
			emp[i].add(k[j] - 1)
			lan[k[j] - 1].add(i)
		if not k[0]:
			num0 += 1
	v = [0] * n
	vi = [0] * m
	for i in range(n):
		if not v[i]:
			v[i] = 1
			for j in emp[i]:
				if not vi[j]:
					dfs(j, v, vi, lan, emp, 1)
			ans += 1
	if num0 == n:
		print(n)
	else:
		print(ans - 1)
main()


import sys, math
from collections import deque, defaultdict
import operator as op
from functools import reduce
from itertools import permutations
import heapq
I = sys.stdin.readline
alpha = 'abcdefghijklmnopqrstuvwxyz'
mod = 10 ** 9 + 7

def ii():
	return int(I().strip())

def li():
	return list(map(int, I().strip().split()))

def mi():
	return map(int, I().strip().split())

def ncr(n, r):
	r = min(r, n - r)
	numer = reduce(op.mul, range(n, n - r, -1), 1)
	denom = reduce(op.mul, range(1, r + 1), 1)
	return numer // denom

def ispali(s):
	i = 0
	j = len(s) - 1
	while i < j:
		if s[i] != s[j]:
			return False
		i += 1
		j -= 1
	return True

def isPrime(n):
	if n <= 1:
		return False
	elif n <= 2:
		return True
	else:
		for i in range(2, int(n ** 0.5) + 1):
			if n % i == 0:
				return False
		return True

def dfs(s, vis, d):
	vis[s] = 1
	for i in d[s]:
		if vis[i] == -1:
			dfs(i, vis, d)

def main():
	(n, m) = mi()
	d = defaultdict(list)
	cnt = 0
	for i in range(1, n + 1):
		tmp = li()
		if tmp[0] == 0:
			cnt += 1
		else:
			for k in tmp[1:]:
				d[i].append(n + k)
				d[n + k].append(i)
	if cnt == n:
		print(n)
	else:
		vis = [-1] * (n + m + 1)
		cnt = -1
		for i in range(1, n + 1):
			if vis[i] == -1:
				cnt += 1
				dfs(i, vis, d)
		print(cnt)
main()


from collections import deque, namedtuple

def dfs(adj, s, visited):
	dq = deque([s])
	while len(dq) > 0:
		now = dq.pop()
		if visited[now]:
			continue
		visited[now] = 1
		for i in adj[now]:
			if not visited[i]:
				dq.append(i)

def main():
	(n, m) = map(int, input().strip().split())
	(adj, check) = ([[] for j in range(n + m)], 0)
	for i in range(n):
		l = [int(j) for j in input().split()][1:]
		check = max(check, len(l))
		for j in l:
			adj[i].append(n + j - 1)
			adj[n + j - 1].append(i)
	(visited, ans) = ([0 for i in range(n + m)], -1)
	if check == 0:
		print(n)
		return
	for i in range(n):
		if not visited[i]:
			dfs(adj, i, visited)
			ans += 1
	print(ans)

def __starting_point():
	main()
__starting_point()


def find(sets, a):
	if a == sets[a]:
		return a
	sets[a] = find(sets, sets[a])
	return sets[a]

def join(sets, a, b):
	a = find(sets, a)
	b = find(sets, b)
	if a != b:
		sets[a] = b
(n, m) = (int(x) for x in input().split())
sets = list(range(n))
langs = []
for i in range(n):
	langs.append(set([int(x) for x in input().split()][1:]))
if all((len(lang) == 0 for lang in langs)):
	print(n)
	return
for i in range(n):
	for lang in langs[i]:
		for j in range(i):
			if lang in langs[j]:
				join(sets, i, j)
print(len(set((find(sets, s) for s in sets))) - 1)


(n, m) = list(map(int, input().split()))
parent = [i for i in range(n + 1)]
size = [1 for i in range(n + 1)]
lang = {i: [] for i in range(1, m + 1)}

def union_set(x, y):
	(xRoot, yRoot) = (find_set(x), find_set(y))
	if xRoot == yRoot:
		return
	if size[xRoot] >= size[yRoot]:
		parent[yRoot] = xRoot
		size[xRoot] += size[yRoot]
		size[yRoot] = 0
	else:
		parent[xRoot] = yRoot
		size[yRoot] += size[xRoot]
		size[xRoot] = 0

def find_set(x):
	while parent[x] != x:
		x = parent[x]
	return x
f = 0
for j in range(n):
	l = list(map(int, input().split()))
	if l[0] == 0:
		continue
	for i in range(1, l[0] + 1):
		f = 1
		lang[l[i]].append(j + 1)
for i in lang:
	if len(lang[i]) == 0:
		continue
	else:
		for j in range(len(lang[i])):
			union_set(lang[i][0], lang[i][j])
for i in range(n):
	parent[i + 1] = find_set(i + 1)
if f:
	print(len(set(parent[1:])) - 1, end=' ')
else:
	print(n)


def unite(setlist, uns):
	res = set()
	indicesToDel = list()
	for un in uns:
		for i in range(len(setlist)):
			if un in setlist[i]:
				if not i in indicesToDel:
					indicesToDel.append(i)
					res = res | setlist[i]
	indicesToDel.sort(key=lambda x: -x)
	for i in indicesToDel:
		del setlist[i]
	if len(res) > 0:
		setlist.append(res | set(uns))
	else:
		setlist.append(set(uns))
(n, m) = map(int, input().split())
noLangs = 0
sets = []
for i in range(n):
	l = list(map(int, input().split()))
	if l[0] == 0:
		noLangs += 1
		continue
	del l[0]
	unite(sets, l)
if noLangs == n:
	print(n)
else:
	print(len(sets) - 1 + noLangs)


from sys import stdin, stdout
cin = stdin.readline
cout = stdout.write

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

def union(x, y):
	xset = find(x)
	yset = find(y)
	if xset == yset:
		return
	if rank[xset] > rank[yset]:
		t = xset
		xset = yset
		yset = t
	parent[xset] = yset
	rank[yset] += rank[xset]
(n, m) = map(int, cin().split())
ans = 0
count = 0
rank = [1] * (m + 1)
parent = [i for i in range(m + 1)]
visited = [0] * (m + 1)
for i in range(n):
	a = list(map(int, cin().split()))
	if a[0] == 0:
		ans += 1
		continue
	visited[a[1]] = 1
	for i in range(2, a[0] + 1):
		union(a[i], a[1])
		visited[a[i]] = 1
for i in range(1, m + 1):
	if visited[i] and find(i) == i:
		count += 1
cout(str(ans + max(0, count - 1)))


def find(n, g):
	if g[n] == n:
		return n
	g[n] = find(g[n], g)
	return g[n]

def union(a, b, g, s):
	roota = find(a, g)
	rootb = find(b, g)
	if roota == rootb:
		return
	if s[roota] < s[rootb]:
		g[roota] = rootb
		s[rootb] += s[roota]
	else:
		g[rootb] = roota
		s[roota] += s[rootb]
langKnown = False
(n, m) = list(map(int, input().split()))
g = [i for i in range(n + m)]
s = [1 for _ in range(n + m)]
for e in range(n):
	langs = list(map(int, input().split()))
	if langs[0] != 0:
		langKnown = True
		for l in langs[1:]:
			union(e + m, l - 1, g, s)
if not langKnown:
	print(n)
else:
	groups = set()
	for i in range(m, m + n):
		root = find(i, g)
		groups.add(root)
	print(len(groups) - 1)


(n, m) = map(int, input().strip().split())
people = {}
graph = {}
for i in range(1, n + 1):
	graph[i] = []
for i in range(n):
	arr = list(map(int, input().strip().split()))
	k = arr.pop(0)
	people[i + 1] = arr
for person in people:
	for language in people[person]:
		for other_person in people:
			if language in people[other_person] and other_person != person:
				graph[person].append(other_person)
if all((len(people[i]) == 0 for i in people)):
	print(n)
	quit()
visited = [False] * (n + 1)

def dfs(node):
	if visited[node]:
		return
	visited[node] = True
	for other in graph[node]:
		dfs(other)

def connectedComponents():
	componenets = 0
	for i in range(1, n + 1):
		if not visited[i]:
			componenets += 1
			dfs(i)
	return componenets
print(connectedComponents() - 1)


def go():
	(n, m) = [int(i) for i in input().split(' ')]
	persons = []
	components = 0
	for i in range(n):
		data = [int(i) for i in input().split(' ')]
		if data[0] == 0:
			components += 1
		persons.append(set(data[1:]))
	visited = set()
	found = False
	for i in range(n):
		if i in visited:
			continue
		current_component = set()
		visited.add(i)
		for l in persons[i]:
			current_component.add(l)
		for j in range(i + 1, n):
			if j in visited:
				continue
			if persons[j].intersection(current_component):
				for l in persons[j]:
					current_component.add(l)
				visited.add(j)
		for j in range(n - 1, i, -1):
			if j in visited:
				continue
			if persons[j].intersection(current_component):
				for l in persons[j]:
					current_component.add(l)
				visited.add(j)
		if current_component:
			found = True
			components += 1
	if not found:
		components += 1
	return components - 1
print(go())


def main():

	def root(a):
		b = f[a]
		while b != a:
			(a, b) = (b, f[b])
		return a
	(n, m) = list(map(int, input().split()))
	f = list(range(n))
	lang = []
	for _ in range(n):
		lang.append(set(list(map(int, input().split()[1:]))))
	for i in range(n):
		for j in range(i):
			if j == root(j) and lang[j].intersection(lang[i]):
				f[j] = i
				lang[i].update(lang[j])
	print(sum((i == root(i) for i in range(n))) - any(lang))

def __starting_point():
	main()
__starting_point()


def inter(li, i, j):
	if len(li[i].intersection(li[j])) > 0:
		return True
	else:
		return False
(n, m) = list(map(int, input().split(' ')))
li = []
allzero = True
for _ in range(n):
	temp = list(map(int, input().split(' ')))
	li.append(set(temp[1:]))
	if temp[0] != 0:
		allzero = False
check = [0] * n
count = -1
j = 0
while j < len(check):
	if check[j] == 0:
		count += 1
		check[j] = 1
		i = 0
		common = []
		while i < n:
			if i == j:
				i += 1
				continue
			if inter(li, i, j):
				common.append(i)
			i += 1
		k = 0
		while k < len(common):
			if check[common[k]] == 0:
				check[common[k]] = 1
				i = 0
				while i < n:
					if check[i] == 1:
						i += 1
						continue
					if i == common[k]:
						i += 1
						continue
					if inter(li, i, common[k]):
						if i not in common:
							common.append(i)
					i += 1
			k += 1
	j += 1
if allzero:
	print(n)
	quit()
print(count)


(n, m) = map(int, input().split())
languages = []
for i in range(n):
	languages.append(list(map(int, input().split()))[1:])
languages.insert(0, [])
neighbors = [[] for i in range(1 + n)]

def SpeakSame(e1, e2):
	for one_language in languages[e1]:
		for another_language in languages[e2]:
			if one_language == another_language:
				return True
	return False
for i in range(1, 1 + n):
	for j in range(1 + i, 1 + n):
		if SpeakSame(i, j):
			neighbors[i].append(j)
			neighbors[j].append(i)
result = [[], [2, 3], [1], [1]]

class Queue:

	def __init__(self):
		self.list = []

	def push(self, value):
		self.list.append(value)

	def pop(self):
		return self.list.pop(0)

	def top(self):
		return self.list[0]

	def empty(self):
		return len(self.list) == 0
visited = [False for i in range(1 + n)]
independentCycles = 0

def BFS(starting):
	connectedComponent = []
	global independentCycles
	BFSq = Queue()
	BFSq.push(starting)
	visited[starting] = True
	while not BFSq.empty():
		m = BFSq.pop()
		connectedComponent.append(m)
		for neighbor in neighbors[m]:
			if not visited[neighbor]:
				visited[neighbor] = True
				BFSq.push(neighbor)
			else:
				independentCycles += 1
	return connectedComponent
count_isolated = 0
ConnectedSets = []
for (idx, visit) in enumerate(visited):
	if visit == False:
		connectedComponent = BFS(idx)
		if len(connectedComponent) == 1:
			count_isolated += 1
		ConnectedSets.append(connectedComponent)
doNotSpeak = 0
for language in languages:
	if len(language) == 0:
		doNotSpeak += 1
if doNotSpeak == len(languages):
	print(len(ConnectedSets) - 1)
else:
	print(len(ConnectedSets) - 2)


import os, sys, math
from io import BytesIO, IOBase
from collections import defaultdict, deque, OrderedDict
import bisect as bi

def yes():
	print('YES')

def no():
	print('NO')

def I():
	return int(input())

def In():
	return map(int, input().split())

def ln():
	return list(map(int, input().split()))

def Sn():
	return input().strip()
BUFSIZE = 8192

def find_gt(a, x):
	i = bi.bisect_left(a, x)
	if i != len(a):
		return i
	else:
		return len(a)

class DSU:

	def __init__(self, n):
		self.parent = [i for i in range(n + 1)]
		self.rank = [0 for i in range(n + 1)]

	def find_set(self, v):
		if v == self.parent[v]:
			return v
		self.parent[v] = self.find_set(self.parent[v])
		return self.parent[v]

	def union(self, a, b):
		a = self.find_set(a)
		b = self.find_set(b)
		if a != b:
			if self.rank[a] < self.rank[b]:
				(a, b) = (b, a)
			self.parent[b] = a
			if self.rank[a] == self.rank[b]:
				self.rank[a] += 1

def solve():
	(n, m) = In()
	l = [list(In()) for i in range(n)]
	dsu = DSU(m)
	ans = 0
	mac = set()
	for i in range(n):
		if l[i][0] >= 2:
			for j in range(2, l[i][0] + 1):
				dsu.union(l[i][1], l[i][j])
				mac.add(l[i][1])
				mac.add(l[i][j])
		elif l[i][0] == 0:
			ans += 1
		else:
			mac.add(l[i][1])
	for i in range(1, m + 1):
		dsu.parent[i] = dsu.find_set(dsu.parent[i])
	done = set()
	for x in dsu.parent[1:]:
		if x in mac:
			done.add(x)
	zp = 0
	if len(done):
		zp = len(done) - 1
	print(zp + ans)
	pass

def main():
	T = 1
	for i in range(T):
		solve()
M = 998244353
P = 1000000007

class FastIO(IOBase):
	newlines = 0

	def __init__(self, file):
		self._fd = file.fileno()
		self.buffer = BytesIO()
		self.writable = 'x' in file.mode or 'r' not in file.mode
		self.write = self.buffer.write if self.writable else None

	def read(self):
		while True:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			if not b:
				break
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines = 0
		return self.buffer.read()

	def readline(self):
		while self.newlines == 0:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			self.newlines = b.count(b'\n') + (not b)
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines -= 1
		return self.buffer.readline()

	def flush(self):
		if self.writable:
			os.write(self._fd, self.buffer.getvalue())
			(self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

	def __init__(self, file):
		self.buffer = FastIO(file)
		self.flush = self.buffer.flush
		self.writable = self.buffer.writable
		self.write = lambda s: self.buffer.write(s.encode('ascii'))
		self.read = lambda : self.buffer.read().decode('ascii')
		self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')
main()


import sys
sys.setrecursionlimit(10 ** 6)

def find(i, parent):
	if parent[i] == i:
		return i
	else:
		parent[i] = find(parent[i], parent)
		return parent[i]

def union(a, b, parent, size):
	xa = find(a, parent)
	xb = find(b, parent)
	if size[xa] > size[xb]:
		parent[xb] = xa
		size[xa] += size[xb]
	else:
		parent[xa] = xb
		size[xb] += size[xa]
ans = 0
(n, m) = list(map(int, input().split()))
parent = []
for i in range(m + 1):
	parent.append(i)
visited = [0] * (m + 1)
size = [1] * (m + 1)
for i in range(n):
	l = list(map(int, input().split()))
	if l[0] == 0:
		ans = ans + 1
	else:
		x = l[1]
		visited[x] = 1
		for j in range(2, len(l)):
			visited[l[j]] = 1
			union(x, l[j], parent, size)
f = -1
for i in range(1, m + 1):
	if parent[i] == i and visited[i] == 1:
		f = f + 1
if f != -1:
	ans = ans + f
print(ans)


import sys
input = sys.stdin.readline

def dfs(i, grp):
	if vis[i]:
		return
	vis[i] = grp
	langs = employees[i]
	for lang in langs:
		for emp in langs_d[lang]:
			dfs(emp, grp)
(n, m) = map(int, input().split())
employees = []
langs_d = {}
for i in range(n):
	langs = list(map(int, input().split()))[1:]
	employees.append(langs)
	for lang in langs:
		if lang in langs_d:
			langs_d[lang].append(i)
		else:
			langs_d[lang] = [i]
if not langs_d.keys():
	print(n)
	exit()
vis = [0] * n
grp = 1
for i in range(n):
	if vis[i]:
		continue
	dfs(i, grp)
	grp += 1
print(max(vis) - 1)


import math
(n, m) = map(int, input().split())
answer = 0
g = []
m = {}
used = [0] * n
not_speak = []
for i in range(n):
	g.append([])

def bfs(start):
	used[start] = 1
	queue = [start]
	while queue != []:
		for u in g[queue[0]]:
			if used[u] == 0:
				used[u] = 1
				queue.append(u)
		del queue[0]
for i in range(n):
	r = list(map(int, input().split()))
	del r[0]
	if r == []:
		not_speak.append(i)
		continue
	else:
		for j in r:
			if j not in m:
				m[j] = i
				g[i].append(i)
			else:
				g[m[j]].append(i)
				g[i].append(m[j])
for i in range(n):
	if i not in not_speak:
		bfs(i)
		break
for i in range(1, n):
	if used[i] == 0 and i not in not_speak:
		answer += 1
		bfs(i)
print(answer + len(not_speak))


(m, n) = map(int, input().split())
adj = [[] for i in range(n)]
vis = [0 for i in range(n)]
mo = [[]]
ans = 0

def dfs(x, tmp):
	vis[x] = 1
	tmp += [x]
	flag = 0
	for i in adj[x]:
		if vis[i] == 0:
			flag = 1
			dfs(i, tmp)
	if flag == 0:
		mo[0] += [tmp]
for i in range(m):
	l = list(map(int, input().split()))
	l = l[1:]
	if l == []:
		ans += 1
	for i in l:
		for j in l:
			adj[i - 1] += [j - 1]
			adj[j - 1] += [i - 1]
for i in range(n):
	if vis[i] != 1 and adj[i] != []:
		dfs(i, [])
mo = mo[0]
mo.sort()
ans += 1
for i in range(len(mo) - 1):
	if mo[i][0] in mo[i + 1]:
		continue
	ans += 1
print(ans - 1)


MOD = 1000000007
MOD2 = 998244353
ii = lambda : int(input())
si = lambda : input()
dgl = lambda : list(map(int, input()))
f = lambda : map(int, input().split())
il = lambda : list(map(int, input().split()))
ls = lambda : list(input())

class DSU:

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

	def find_set(self, a):
		if a == self.parent[a]:
			return a
		self.parent[a] = self.find_set(self.parent[a])
		return self.parent[a]

	def union_set(self, a, b):
		x = self.find_set(a)
		y = self.find_set(b)
		if x != y:
			if self.rank[x] < self.rank[y]:
				(x, y) = (y, x)
			self.rank[x] += self.rank[y]
			self.parent[y] = x
(n, m) = f()
l = []
c = 0
for _ in range(n):
	(a, *b) = f()
	b = set(b)
	if len(b):
		l.append(b)
	else:
		c += 1
nn = len(l)
dsu = DSU(nn)
par = -1
for i in range(1, m + 1):
	for j in range(nn):
		if i in l[j]:
			if par == -1:
				par = j
			else:
				dsu.union_set(par, j)
	par = -1
print(max(len(set((dsu.find_set(i) for i in range(nn)))) - 1, 0) + c)


from collections import defaultdict

def DFS(graph, x, visited):
	stack = [x]
	while len(stack):
		temp = stack.pop()
		visited[temp] = 1
		for j in graph[temp]:
			if visited[j] == 0:
				stack.append(j)
(n, m) = map(int, input().split())
d = defaultdict(set)
graph = defaultdict(list)
for j in range(n):
	l = list(map(int, input().split()))
	l = list(l[1:])
	for k in l:
		d[j + 1].add(k)
for j in range(1, n + 1):
	for k in range(j + 1, n + 1):
		l = d[j].intersection(d[k])
		if len(l):
			graph[j].append(k)
			graph[k].append(j)
visited = [0 for i in range(n + 1)]
c = 0
for j in range(1, n + 1):
	if visited[j] == 0:
		DFS(graph, j, visited)
		c = c + 1
flag = 0
for j in range(1, n + 1):
	if len(d[j]) != 0:
		flag = 1
if flag == 0:
	print(c)
else:
	print(c - 1)


(n, m) = map(int, input().split())
s = [0] * n
adj = [[] for i in range(n)]
for i in range(n):
	l = list(map(int, input().split()))
	for k in l[1:]:
		s[i] |= 1 << k
if sum(s) == 0:
	print(n)
	exit(0)
for i in range(n):
	for j in range(n):
		if s[i] & s[j] != 0:
			adj[i].append(j)
visited = [0] * n

def dfs(v):
	if visited[v]:
		return
	visited[v] = 1
	for u in adj[v]:
		dfs(u)
count = 0
for v in range(n):
	if not visited[v]:
		dfs(v)
		count += 1
print(count - 1)


import os
import sys
from io import BytesIO, IOBase
import math
from decimal import *
from collections import defaultdict
getcontext().prec = 25
MOD = pow(10, 9) + 7
BUFSIZE = 8192

class FastIO(IOBase):
	newlines = 0

	def __init__(self, file):
		self._fd = file.fileno()
		self.buffer = BytesIO()
		self.writable = 'x' in file.mode or 'r' not in file.mode
		self.write = self.buffer.write if self.writable else None

	def read(self):
		while True:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			if not b:
				break
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines = 0
		return self.buffer.read()

	def readline(self):
		while self.newlines == 0:
			b = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
			self.newlines = b.count(b'\n') + (not b)
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines -= 1
		return self.buffer.readline()

	def flush(self):
		if self.writable:
			os.write(self._fd, self.buffer.getvalue())
			(self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

	def __init__(self, file):
		self.buffer = FastIO(file)
		self.flush = self.buffer.flush
		self.writable = self.buffer.writable
		self.write = lambda s: self.buffer.write(s.encode('ascii'))
		self.read = lambda : self.buffer.read().decode('ascii')
		self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')

def dfs(s):
	for i in s:
		for j in ml[i]:
			if not visited[j]:
				visited[j] = True
				for k in l[j][1:]:
					s.append(k)
(n, k) = map(int, input().split(' '))
ml = [[] for i in range(k + 1)]
l = []
ans = False
visited = [False] * n
for _ in range(n):
	a = list(map(int, input().split(' ')))
	l.append(a)
	if a[0] != 0:
		ans = True
	for i in range(1, a[0] + 1):
		ml[a[i]].append(_)
c = 0
for i in range(n):
	s = list()
	if not visited[i]:
		if len(l[i]) == 1:
			c += 1
			visited[i] = True
		else:
			c += 1
			visited[i] = True
			s = l[i][1:]
			dfs(s)
if ans:
	print(c - 1)
else:
	print(n)


(n, m) = map(int, input().split())
groups = list(({i} for i in range(1, m + 1)))
alive = set()
debug = False

def dbg(var):
	if debug:
		print('[dbg]  {} = {}'.format(var, eval(var)))
cnt = 0
for i in range(n):
	langs = set(map(int, input().split()[1:]))
	if len(langs) < 1:
		cnt += 1
		continue
	dbg('langs')
	alive.update(langs)
	joined = []
	for g in groups:
		if langs.intersection(g):
			joined.append(g)
	dbg('joined')
	new_set = set()
	for j in joined:
		new_set.update(j)
		groups.remove(j)
	dbg('new_set')
	groups.append(new_set)
	dbg('groups')
	dbg('0')
for i in range(1, m + 1):
	if i not in alive:
		dbg('{i}')
		groups.remove({i})
if not alive:
	ans = n
else:
	ans = len(groups) - 1 + cnt
print(ans)


(n, k) = list(map(int, input().split()))
ans = 0
t = []
for _ in range(n):
	l = list(map(int, input().split()))
	if l[0] == 0:
		ans += 1
	else:
		t += [set(l[1:])]
i = 0
while i < len(t):
	for j in t[i + 1:]:
		if t[i] & j:
			t[i] |= j
			t.remove(j)
			break
	else:
		i += 1
print(max(0, len(t) - 1) + ans)


import sys
input = sys.stdin.readline
from math import log2, log, ceil

def swaparr(arr, a, b):
	temp = arr[a]
	arr[a] = arr[b]
	arr[b] = temp

def gcd(a, b):
	if a == 0:
		return b
	return gcd(b % a, a)

def nCr(n, k):
	if k > n - k:
		k = n - k
	res = 1
	for i in range(k):
		res = res * (n - i)
		res = res / (i + 1)
	return res

def upper_bound(a, x, lo=0):
	hi = len(a)
	while lo < hi:
		mid = (lo + hi) // 2
		if a[mid] < x:
			lo = mid + 1
		else:
			hi = mid
	return lo

def primefs(n):
	primes = {}
	while n % 2 == 0:
		primes[2] = primes.get(2, 0) + 1
		n = n // 2
	for i in range(3, int(n ** 0.5) + 2, 2):
		while n % i == 0:
			primes[i] = primes.get(i, 0) + 1
			n = n // i
	if n > 2:
		primes[n] = primes.get(n, 0) + 1
	return primes

def power(x, y, p):
	res = 1
	x = x % p
	if x == 0:
		return 0
	while y > 0:
		if y & 1 == 1:
			res = res * x % p
		y = y >> 1
		x = x * x % p
	return res

def swap(a, b):
	temp = a
	a = b
	b = temp
	return (a, b)

def find(x, link):
	p = x
	while p != link[p]:
		p = link[p]
	while x != p:
		nex = link[x]
		link[x] = p
		x = nex
	return p

def union(x, y, link, size):
	x = find(x, link)
	y = find(y, link)
	if size[x] < size[y]:
		(x, y) = swap(x, y)
	if x != y:
		size[x] += size[y]
		link[y] = x

def sieve(n):
	prime = [True for i in range(n + 1)]
	p = 2
	while p * p <= n:
		if prime[p] == True:
			for i in range(p * p, n + 1, p):
				prime[i] = False
		p += 1
	return prime
MAXN = int(1000000.0 + 5)

def spf_sieve():
	spf[1] = 1
	for i in range(2, MAXN):
		spf[i] = i
	for i in range(4, MAXN, 2):
		spf[i] = 2
	for i in range(3, ceil(MAXN ** 0.5), 2):
		if spf[i] == i:
			for j in range(i * i, MAXN, i):
				if spf[j] == j:
					spf[j] = i

def factoriazation(x):
	ret = {}
	while x != 1:
		ret[spf[x]] = ret.get(spf[x], 0) + 1
		x = x // spf[x]
	return ret

def int_array():
	return list(map(int, input().strip().split()))

def str_array():
	return input().strip().split()
MOD = int(1000000000.0) + 7
CMOD = 998244353
INF = float('inf')
NINF = -float('inf')
(n, m) = int_array()
link = [i for i in range(m)]
size = [1] * m
ans = 0
parent = []
used = set()
for _ in range(n):
	a = int_array()
	c = a[0]
	a = a[1:]
	for i in a:
		used.add(i - 1)
	if c == 0:
		ans += 1
	elif c == 1:
		parent.append(a[-1] - 1)
	else:
		for i in range(c - 1):
			union(a[i] - 1, a[i + 1] - 1, link, size)
		parent.append(a[-1] - 1)
for i in range(len(parent)):
	parent[i] = find(link[parent[i]], link)
pset = set(parent)
mset = used.intersection(pset)
mikasa = len(mset) - 1 if len(mset) > 0 else 0
print(ans + mikasa)


from sys import stdin
input = lambda : stdin.readline().strip()
from math import ceil, sqrt, factorial, gcd

def traverse(a):
	stack = [a]
	while stack:
		x = stack.pop()
		if x not in visited:
			for i in graph[x]:
				if i not in visited:
					stack.append(i)
			visited.add(x)
(n, m) = map(int, input().split())
graph = {i: set() for i in range(1, n + 1)}
language = {i: set() for i in range(1, m + 1)}
zero_wala = set()
count1 = 0
for i in range(n):
	(k, *lis) = map(int, input().split())
	t = i + 1
	if k == 0:
		count1 += 1
		zero_wala.add(i + 1)
	for j in lis:
		for k in language[j]:
			graph[k].add(t)
			graph[t].add(k)
		language[j].add(t)
visited = set()
count = 0
for i in graph:
	if i not in zero_wala != 0:
		if i not in visited:
			count += 1
			traverse(i)
if count > 0:
	ans = count + count1 - 1
else:
	ans = count1
print(ans)


class Graph:

	def __init__(self, V, n):
		self.v = V + 1
		self.n = n
		self.graph = [[] for i in range(self.v)]

	def addEdge(self, x, y):
		self.graph[x].append(y)
		self.graph[y].append(x)

	def NCC(self):
		vis = [False] * self.v
		count = 0
		for v in range(1, self.n + 1):
			if vis[v] == False:
				self.DFSUtil(v, vis)
				count += 1
		return count

	def DFSUtil(self, v, visited):
		visited[v] = True
		for i in self.graph[v]:
			if not visited[i]:
				self.DFSUtil(i, visited)
(n, m) = [int(x) for x in input().split()]
arr = {}
for i in range(n):
	temp = [int(x) for x in input().split()]
	del temp[0]
	temp.insert(0, i + 1)
	if len(temp) == 1:
		arr[temp[0]] = []
	else:
		arr[temp[0]] = temp[1:]
g = Graph(n + m, n)
for k in arr.keys():
	for x in arr[k]:
		g.addEdge(k, x + n)
isempty = True
for k in arr.keys():
	if len(arr[k]) != 0:
		isempty = False
		break
if isempty:
	print(n)
else:
	print(g.NCC() - 1)


lab = []
cou = []

def makeSet(n):
	global lab, cou
	lab = [-1 for i in range(n + 1)]
	cou = [1 for i in range(n + 1)]

def getroot(u):
	if lab[u] == -1:
		return u
	lab[u] = getroot(lab[u])
	return lab[u]

def union(a, b):
	if cou[a] > cou[b]:
		cou[a] += cou[b]
		lab[b] = a
	else:
		cou[b] += cou[a]
		lab[a] = b
(m, n) = map(int, input().split())
makeSet(m)
graph = [[] for i in range(m + 1)]
ans = 0
for i in range(1, m + 1):
	a = list(map(int, input().split()))
	if a[0] > 0:
		for j in range(1, len(a)):
			graph[i].append(a[j])
	if a[0] == 0:
		ans += 1
for i in range(1, n + 1):
	new = 0
	cnt = 0
	for j in range(len(graph)):
		if i in graph[j]:
			pre = new
			new = j
			cnt += 1
		if cnt > 1:
			pre = getroot(pre)
			new = getroot(new)
			if pre != new:
				union(pre, new)
if ans == len(lab) - 1:
	print(len(lab) - 1)
else:
	print(lab.count(-1) - 2)


import sys, math, bisect
from random import randint
inf = float('inf')
mod = 10 ** 9 + 7

def lcm(a, b):
	return int(a / math.gcd(a, b) * b)

def gcd(a, b):
	return int(math.gcd(a, b))

def tobinary(n):
	return bin(n)[2:]

def binarySearch(a, x):
	i = bisect.bisect_left(a, x)
	if i != len(a) and a[i] == x:
		return i
	else:
		return -1

def lowerBound(a, x):
	i = bisect.bisect_left(a, x)
	if i:
		return i - 1
	else:
		return -1

def upperBound(a, x):
	i = bisect.bisect_right(a, x)
	if i != len(a) + 1 and a[i - 1] == x:
		return i - 1
	else:
		return -1

def primesInRange(n):
	ans = []
	prime = [True for i in range(n + 1)]
	p = 2
	while p * p <= n:
		if prime[p] == True:
			for i in range(p * p, n + 1, p):
				prime[i] = False
		p += 1
	for p in range(2, n + 1):
		if prime[p]:
			ans.append(p)
	return ans

def primeFactors(n):
	factors = []
	while n % 2 == 0:
		factors.append(2)
		n = n // 2
	for i in range(3, int(math.sqrt(n)) + 1, 2):
		while n % i == 0:
			factors.append(i)
			n = n // i
	if n > 2:
		factors.append(n)
	return factors

def isPrime(n, k=5):
	if n < 2:
		return True
	for i in range(0, k):
		a = randint(1, n - 1)
		if pow(a, n - 1, n) != 1:
			return False
	return True
from collections import deque, defaultdict, Counter
from heapq import heappush, heappop
import string
(n, m) = map(int, input().split())
arr = []
for _ in range(n):
	arr.append(list(map(int, input().split())))
persons = {i: [] for i in range(1, n + 1)}
languages = {i + n: [] for i in range(1, m + 1)}
for i in range(n):
	for j in range(1, len(arr[i])):
		persons[i + 1].append(n + arr[i][j])
for i in range(1, m + 1):
	for j in persons:
		if i + n in persons[j]:
			languages[i + n].append(j)
visited = []

def dfs(v, visited):
	visited.append(v)
	for language in persons[v]:
		for person in languages[language]:
			if person not in visited:
				dfs(person, visited)
cc = 0
for i in range(1, n + 1):
	if i not in visited:
		dfs(i, visited)
		cc += 1
summ = 0
for i in arr:
	summ += sum(i)
if summ == 0:
	print(n)
else:
	print(cc - 1)


3.4
(n, m) = list(map(int, input().split()))
arr = [[] for i in range(n)]
used = [0 for i in range(n)]
ans = 0

def dfs(vertex):
	used[vertex] = 1
	langs = arr[vertex]
	for lang in langs:
		for (ind, next_vertex) in enumerate(arr):
			if lang in next_vertex and used[ind] == 0:
				dfs(ind)
test = 0
for i in range(n):
	arr[i] = list(map(int, input().split()[1:]))
	if len(arr[i]) == 0:
		test += 1
for node in range(n):
	if used[node] == 0:
		dfs(node)
		ans += 1
print(ans - 1 + (1 if test == n else 0))


from sys import stdin
input = stdin.readline
R = lambda : map(int, input().split())
I = lambda : int(input())

def find(x):
	while x != lng[x]:
		lng[x] = lng[lng[x]]
		x = lng[x]
	return x
(n, m) = R()
lng = [i for i in range(m + 1)]
d = []
u = []
s = 0
for i in range(n):
	x = list(R())
	if not x[0]:
		s += 1
		continue
	u.extend(x[1:])
	for el in x[2:]:
		lng[find(el)] = find(x[1])
print(len(set([find(i) for i in set(u)])) + s - (1 if u else 0))


def dfs(g, used, n):
	used[n] = True
	for to in g[n]:
		if not used[to]:
			dfs(g, used, to)
(x, y) = map(int, input().split())
g = [set() for _ in range(x)]
v = [[] for _ in range(y)]
p2l = [0] * x
cnt_0 = 0
for i in range(x):
	l = list(map(int, input().split()))[1:]
	for j in l:
		v[j - 1].append(i)
	p2l[i] = len(l)
	cnt_0 += int(p2l[i] == 0)
for k in range(y):
	for i in v[k]:
		for j in v[k]:
			g[i].add(j)
used = [False] * x
cnt_k = 0
for i in range(x):
	if not used[i] and p2l[i] > 0:
		cnt_k += 1
		dfs(g, used, i)
print(cnt_0 + cnt_k - (1 if cnt_k > 0 else 0))


from queue import Queue

def bfs(src, graph, visited):
	q = Queue()
	q.put(src)
	while not q.empty():
		curr = q.get()
		if curr not in visited:
			visited.add(curr)
			for child in graph[curr]:
				if child not in visited:
					q.put(child)

def solve():
	(n, m) = map(int, input().split())
	lGraph = [[] for _ in range(m + 1)]
	zeroGroup = 0
	for i in range(n):
		languageArray = list(map(int, input().split()))
		ln = languageArray[0] + 1
		if ln == 1:
			lGraph[0].append(i)
			zeroGroup += 1
		for j in range(1, ln):
			lKnown = languageArray[j]
			lGraph[lKnown].append(i)
	graphSet = [set() for _ in range(n)]
	for i in range(m + 1):
		sameLangArray = lGraph[i]
		ln = len(sameLangArray)
		for j in range(ln):
			for k in range(j + 1, ln):
				src = sameLangArray[j]
				dest = sameLangArray[k]
				graphSet[src].add(dest)
				graphSet[dest].add(src)
	visited = set()
	res = 0
	for i in range(n):
		if i not in visited:
			res += 1
			bfs(i, graphSet, visited)
	if zeroGroup > 0:
		if res == 1:
			print(zeroGroup)
		else:
			print(res + zeroGroup - 2)
		return
	print(res - 1)
	return
solve()


from sys import stdin, stdout
illiterate = 0

def find(node):
	if dsu[node] == node:
		return node
	else:
		dsu[node] = find(dsu[node])
		return dsu[node]
(n, m) = map(int, stdin.readline().strip().split())
dsu = [i for i in range(n + 1)]
language = [0] * (m + 1)
for j in range(n):
	arr = list(map(int, stdin.readline().strip().split()))
	if arr[0] != 0:
		for i in arr[1:]:
			if not language[i]:
				language[i] = find(j + 1)
			else:
				dsu[find(language[i])] = find(j + 1)
	else:
		illiterate += 1
count = 0
for i in range(1, n + 1):
	if i == dsu[i]:
		count += 1
if count == illiterate:
	print(n)
else:
	print(count - 1)


(n, m) = map(int, input().split())
p = [None for i in range(m + 1)]
r = [0 for i in range(m + 1)]
k = 0

def get(x):
	while p[x] != x:
		x = p[x]
	return x

def union(x, y):
	x = get(x)
	y = get(y)
	if x == y:
		return 0
	if r[x] > r[y]:
		(x, y) = (y, x)
	p[x] = y
	if r[x] == r[y]:
		r[y] += 1
	return 1
for i in range(n):
	s = list(map(int, input().split()))
	if s[0] == 0:
		k += 1
	for j in range(1, s[0] + 1):
		if p[s[j]] == None:
			p[s[j]] = s[j]
	for j in range(1, s[0]):
		union(s[j], s[j + 1])
ok = False
for i in range(1, m + 1):
	if i == p[i]:
		k += 1
		ok = True
print(k - 1 if ok else k)


(employees, k) = (set(), 0)
for _ in range(int(input().split()[0])):
	languages = tuple(input().split()[1:])
	if languages:
		employees.add(languages)
	else:
		k += 1
if employees:
	company_languages = set(employees.pop())
	while employees:
		contactable = set()
		for languages in employees:
			if set(languages) & set(company_languages):
				company_languages |= set(languages)
				contactable.add(languages)
		employees -= contactable
		if not contactable:
			(company_languages, k) = (company_languages | set(employees.pop()), k + 1)
print(k)


I = lambda : list(map(int, input().split()))
(n, m) = I()
r = 0
t = []
for _ in range(n):
	l = I()
	if l[0] == 0:
		r += 1
	else:
		t += [set(l[1:])]
i = 0
while i < len(t):
	for j in t[i + 1:]:
		if t[i] & j:
			t[i] |= j
			t.remove(j)
			break
	else:
		i += 1
print(max(0, len(t) - 1) + r)


(n, m) = list(map(int, input().split()))
all_lang_num = 0
lang = []
for i in range(n):
	l = list(map(int, input().split()))
	lang.append(set(l[1:]))
	all_lang_num += l[0]
edges = []
for i in range(n):
	p = set()
	for j in range(n):
		if len(lang[i] & lang[j]) != 0:
			p.add(j)
	edges.append(p)
unprocessed = set(range(n))
components = 0
while len(unprocessed) != 0:
	components += 1
	queue = {min(unprocessed)}
	while len(queue) != 0:
		v = min(queue)
		queue |= unprocessed & edges[v]
		unprocessed.remove(v)
		queue.remove(v)
if all_lang_num == 0:
	print(n)
else:
	print(components - 1)


(n, m) = [int(i) for i in input().split()]
languages = []
ans = 0
for i in range(n):
	tmp = set([int(j) for j in input().split()[1:]])
	if tmp:
		languages.append(tmp)
	else:
		n -= 1
		ans += 1
i = 0
while i < n:
	j = i + 1
	while j < n:
		if languages[i] & languages[j]:
			languages[i] = languages[i] | languages[j]
			del languages[j]
			n -= 1
		else:
			j += 1
	i += 1
i = 0
while i < n:
	j = i + 1
	while j < n:
		if languages[i] & languages[j]:
			languages[i] = languages[i] | languages[j]
			del languages[j]
			n -= 1
		else:
			j += 1
	i += 1
if n:
	ans += n - 1
print(ans)


(n, m) = map(int, input().split())
q = [[] for i in range(n + 1)]
(r, s) = ([0] * (m + 1), 0)
for i in range(1, n + 1):
	t = list(map(int, input().split()))
	if t[0]:
		t = t[1:]
		d = set([r[j] for j in t])
		if 0 in d:
			d.remove(0)
		if len(d):
			for j in d:
				for k in q[j]:
					r[k] = i
				q[i].extend(q[j])
				q[j] = []
			t = [j for j in t if r[j] == 0]
			for k in t:
				r[k] = i
			q[i].extend(t)
		else:
			for k in t:
				r[k] = i
			q[i] = t
	else:
		s += 1
print(s + max(sum((len(i) > 0 for i in q)) - 1, 0))


from sys import stdin, stdout, setrecursionlimit
from math import floor, gcd, fabs, factorial, fmod, sqrt, inf, log, log2
from random import seed, randint
from datetime import datetime
from collections import defaultdict as dd, deque
from heapq import merge, heapify, heappop, heappush, nsmallest
from bisect import bisect_left as bl, bisect_right as br, bisect
from collections import defaultdict as dd
mod = pow(10, 9) + 7
mod2 = 998244353

def inp():
	return stdin.readline().strip()

def iinp():
	return int(inp())

def out(var, end='\n'):
	stdout.write(str(var) + '\n')

def outa(*var, end='\n'):
	stdout.write(' '.join(map(str, var)) + end)

def lmp():
	return list(mp())

def mp():
	return map(int, inp().split())

def smp():
	return map(str, inp().split())

def l1d(n, val=0):
	return [val for i in range(n)]

def l2d(n, m, val=0):
	return [l1d(m, val) for j in range(n)]

def remadd(x, y):
	return 1 if x % y else 0

def ceil(a, b):
	return (a + b - 1) // b

def def_value():
	return 0

def def_inf():
	return inf
(n, m) = mp()
adj = dd(list)
for i in range(n):
	adj[i + 1].append(i)
	adj[i + 1].pop()
mc = []
for i in range(n):
	a = lmp()
	mc.append(a[0])
	for j in a[1:]:
		adj[i + 1].append(j + n)
		adj[j + n].append(i + 1)
if sum(mc) > 0:
	ans = -1
	mark = l1d(n + m + 1)
	st = []
	for i in adj.keys():
		if mark[i] == 0:
			ans += 1
			mark[i] += 1
			st.append(i)
			while len(st) > 0:
				x = st.pop()
				for y in adj[x]:
					if mark[y] == 0:
						mark[y] += 1
						st.append(y)
	print(ans)
else:
	print(n)


parent = []
ranks = []
lang = []

def makeSet(n):
	global parent, ranks
	parent = [i for i in range(n)]
	ranks = [0 for _ in range(n)]

def findSet(u):
	global parent
	if parent[u] != u:
		parent[u] = findSet(parent[u])
	return parent[u]

def unionSet(u, v):
	global parent, ranks
	up = findSet(u)
	vp = findSet(v)
	if up == vp:
		return 0
	if ranks[up] > ranks[vp]:
		parent[vp] = up
	elif ranks[up] < ranks[vp]:
		parent[up] = vp
	else:
		ranks[up] += 1
		parent[vp] = up
	return 1

def main():
	global lang
	(n, m) = map(int, input().split())
	lang = [[] for _ in range(m + 1)]
	makeSet(n + 1)
	compoment = n
	no_lang = 0
	for i in range(1, n + 1):
		arr = list(map(int, input().split()))
		k = arr.pop(0)
		if k == 0:
			no_lang += 1
		for x in arr:
			lang[x].append(i)
	for i in range(1, m + 1):
		for j in range(len(lang[i]) - 1):
			a = lang[i][j]
			b = lang[i][j + 1]
			compoment -= unionSet(a, b)
	if no_lang == n:
		print(n)
	else:
		print(compoment - 1)
main()


inp = lambda : [*map(int, input().split())]
(n, m) = inp()
ans = []
for i in range(n):
	ans.append(set(inp()[1:]))
i = 0
while i <= len(ans):
	for j in ans[i + 1:]:
		if len(ans[i] & j) != 0:
			ans[i] |= j
			ans.remove(j)
			break
	else:
		i += 1
f = False
for i in ans:
	if len(i) > 0:
		f = True
		break
print(len(ans) - (1 if f else 0))


(n, m) = list(map(int, input().split()))
arr = []
for _ in range(n):
	lang = list(map(int, input().split()))
	lang = lang[1:]
	lang = set(lang)
	f = 0
	for i in range(len(arr)):
		x = arr[i].intersection(lang)
		if x != set():
			arr[i].update(lang)
			f = 1
			break
	if f == 0:
		arr.append(lang)
ans = []
visited = [False for i in range(len(arr))]
for i in range(len(visited)):
	if visited[i] == False:
		visited[i] = True
		y = arr[i]
		for j in range(len(visited)):
			if visited[j] == False:
				x = y.intersection(arr[j])
				if x != set():
					visited[j] = True
					y.update(arr[j])
		ans.append(y)
cnt = 0
m = 0
for i in ans:
	if i == set():
		cnt += 1
	else:
		m += 1
if m == 0:
	print(cnt)
else:
	print(m - 1 + cnt)


(n, m) = map(int, input().split())
sz = [1 for _ in range(n + m)]
par = [-1 for _ in range(n + m)]

def find(c):
	if par[c] == -1:
		return c
	par[c] = find(par[c])
	return par[c]

def join(a, b):
	a = find(a)
	b = find(b)
	if a == b:
		return
	if sz[a] > sz[b]:
		par[b] = a
		sz[a] += sz[b]
	else:
		par[a] = b
		sz[b] += sz[a]
languages = set()
for i in range(n):
	edges = [int(x) for x in input().split()]
	for j in edges[1:]:
		join(i, n + j - 1)
		languages.add(n + j - 1)
unique = set()
for i in range(n):
	unique.add(find(i))
if len(languages) == 0:
	unique.add(-1)
print(len(unique) - 1)


v0 = 0
l = []
(n, m) = (int(x) for x in input().split())
for i in range(n):
	l2 = []
	curs = set([int(x) for x in input().split()][1:])
	if not curs:
		v0 += 1
		continue
	for s in l:
		if curs & s:
			curs |= s
		else:
			l2.append(s)
	l2.append(curs)
	l = l2
print(v0 + max(0, len(l) - 1))


class Node:

	def __init__(self):
		self.visited = False
		self.neighbors = []

def dfs(node):
	if node.visited == True:
		return
	node.visited = True
	for neighbor in node.neighbors:
		dfs(neighbor)
(n, m) = map(int, input().split())
languages = [None]
Employees = [None]
for i in range(m):
	languages.append(Node())
for i in range(n):
	Employees.append(Node())
cntZ = 0
for i in range(1, n + 1):
	arr = list(map(int, input().split()))
	if arr[0] == 0:
		cntZ += 1
		continue
	for j in range(1, len(arr)):
		x = arr[j]
		languages[x].neighbors.append(Employees[i])
		Employees[i].neighbors.append(languages[x])
count = 0
for i in range(1, n + 1):
	if not Employees[i].visited:
		dfs(Employees[i])
		count += 1
if cntZ == n:
	print(count)
else:
	print(count - 1)


def main():

	def root(a):
		b = f[a]
		while b != a:
			(a, b) = (b, f[b])
		return a
	(n, m) = list(map(int, input().split()))
	f = list(range(n))
	lang = []
	for _ in range(n):
		ll = [False] * m
		for i in map(int, input().split()[1:]):
			ll[i - 1] = True
		lang.append(ll)
	for (i, langi) in enumerate(lang):
		for j in range(i):
			if j == root(j) and any((a and b for (a, b) in zip(lang[i], lang[j]))):
				f[j] = i
				for (_, flag) in enumerate(lang[j]):
					langi[_] |= flag
	print(sum((i == root(i) for i in range(n))) - any(map(any, lang)))

def __starting_point():
	main()
__starting_point()


def mapit():
	temp = list(map(int, input().split()))
	return temp

def solution():
	(n, m) = mapit()
	visited = [False] * (n + 1)
	paths = {}
	lans = {}
	without_lan = set()
	for i in range(1, n + 1):
		tp = mapit()
		if tp[0] != 0:
			lans[i] = set(tp[1:])
		else:
			without_lan.add(i)
	for i in range(1, n + 1):
		if i not in without_lan:
			paths[i] = []
			for j in range(1, n + 1):
				if i != j and j not in without_lan:
					if len(lans[i].intersection(lans[j])) != 0:
						paths[i].append(j)
	sol = len(without_lan)

	def dfs(curr):
		visited[curr] = True
		for neighbor in paths[curr]:
			if visited[neighbor] == False:
				dfs(neighbor)
		return
	first = True
	for key in paths:
		if visited[key] == False:
			if paths[key] != []:
				dfs(key)
			if first:
				first = False
			else:
				sol += 1
	print(sol)
	return
solution()


(m, n) = map(int, input().strip().split())
arr = []
v = 0
for k in range(m):
	list1 = list(map(int, input().strip().split()))
	list1 = list1[1:]
	if list1 == []:
		v += 1
	else:
		arr.append(set(list1))
i = 0
while i < len(arr):
	flag = True
	for j in arr[i + 1:]:
		if arr[i] & j:
			arr[i] |= j
			arr.remove(j)
			flag = False
			break
	if flag:
		i += 1
if len(arr) == 0:
	print(v)
else:
	print(v + len(arr) - 1)


d = list(map(int, input().split(' ')))
n = d[0]
m = d[1]
flag = True
langs = [[] for i in range(m)]
for i in range(n):
	d = list(map(int, input().split()))
	flag = flag and d[0] == 0
	for j in range(1, len(d)):
		langs[d[j] - 1].append(i)
if flag:
	print(n)
else:
	graph = [[] for i in range(n)]
	for i in range(m):
		for j in range(len(langs[i])):
			for k in range(j + 1, len(langs[i])):
				v1 = langs[i][j]
				v2 = langs[i][k]
				graph[v1].append(v2)
				graph[v2].append(v1)
	used = [-1 for i in range(n)]

	def dfs(num, color):
		used[num] = color
		for v in graph[num]:
			if used[v] == -1:
				dfs(v, color)
	color = -1
	for i in range(n):
		if used[i] == -1:
			color += 1
			dfs(i, color)
	print(color)


from sys import stdin, stdout
cin = stdin.readline
cout = stdout.write

def bfs(x):
	visited[x] = 1
	qu = [x]
	while len(qu) > 0:
		for a in v[qu[0]]:
			if visited[a]:
				continue
			visited[a] = 1
			qu.append(a)
		qu.pop(0)
(n, m) = map(int, cin().split())
v = [set() for _ in range(m + 1)]
ans = 0
count = 0
for i in range(n):
	a = list(map(int, cin().split()))
	if a[0] == 0:
		ans += 1
		continue
	for i in a[1:]:
		v[i].add(a[1])
		v[a[1]].add(i)
visited = [0] * (m + 1)
for i in range(1, m + 1):
	if not v[i] or visited[i]:
		continue
	bfs(i)
	count += 1
cout(str(ans + max(0, count - 1)))


(n, m) = map(int, input().split())
connected_component = []
to_add = 0
for _ in range(n):
	a = list(map(int, input().split()))
	if a[0] == 0:
		to_add += 1
	else:
		connected_component.append(set(a[1:]))
i = 0
while i < len(connected_component):
	for j in connected_component[i + 1:]:
		if connected_component[i] & j:
			connected_component[i] |= j
			connected_component.remove(j)
			break
	else:
		i += 1
print(max(0, len(connected_component) - 1) + to_add)


import sys
input = sys.stdin.readline
I = lambda : list(map(int, input().split()))
(n, m) = I()
t = list(range(201))

def find(u):
	if t[u] == u:
		return u
	else:
		t[u] = find(t[u])
	return t[u]

def join(u, v):
	x = find(u)
	y = find(v)
	if x != y:
		t[x] = y
s = set()
ans = 0
for i in range(n):
	(a, *b) = I()
	if a == 0:
		ans += 1
	for j in b:
		join(b[0], j)
		s.add(j)
ss = set()
for i in s:
	ss.add(find(i))
print(max(0, len(ss) - 1) + ans)


import sys
import math
import heapq
import bisect
from collections import Counter
from collections import defaultdict
from io import BytesIO, IOBase
import string

class FastIO(IOBase):
	newlines = 0

	def __init__(self, file):
		import os
		self.os = os
		self._fd = file.fileno()
		self.buffer = BytesIO()
		self.writable = 'x' in file.mode or 'r' not in file.mode
		self.write = self.buffer.write if self.writable else None
		self.BUFSIZE = 8192

	def read(self):
		while True:
			b = self.os.read(self._fd, max(self.os.fstat(self._fd).st_size, self.BUFSIZE))
			if not b:
				break
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines = 0
		return self.buffer.read()

	def readline(self):
		while self.newlines == 0:
			b = self.os.read(self._fd, max(self.os.fstat(self._fd).st_size, self.BUFSIZE))
			self.newlines = b.count(b'\n') + (not b)
			ptr = self.buffer.tell()
			(self.buffer.seek(0, 2), self.buffer.write(b), self.buffer.seek(ptr))
		self.newlines -= 1
		return self.buffer.readline()

	def flush(self):
		if self.writable:
			self.os.write(self._fd, self.buffer.getvalue())
			(self.buffer.truncate(0), self.buffer.seek(0))

class IOWrapper(IOBase):

	def __init__(self, file):
		self.buffer = FastIO(file)
		self.flush = self.buffer.flush
		self.writable = self.buffer.writable
		self.write = lambda s: self.buffer.write(s.encode('ascii'))
		self.read = lambda : self.buffer.read().decode('ascii')
		self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))
input = lambda : sys.stdin.readline().rstrip('\r\n')

def get_int():
	return int(input())

def get_ints():
	return list(map(int, input().split(' ')))

def get_int_grid(n):
	return [get_ints() for _ in range(n)]

def get_str():
	return input().split(' ')

def yes_no(b):
	if b:
		return 'YES'
	else:
		return 'NO'

def binary_search(good, left, right, delta=1, right_true=False):
	limits = [left, right]
	while limits[1] - limits[0] > delta:
		if delta == 1:
			mid = sum(limits) // 2
		else:
			mid = sum(limits) / 2
		if good(mid):
			limits[int(right_true)] = mid
		else:
			limits[int(~right_true)] = mid
	if good(limits[int(right_true)]):
		return limits[int(right_true)]
	else:
		return False

def prefix_sums(a, drop_zero=False):
	p = [0]
	for x in a:
		p.append(p[-1] + x)
	if drop_zero:
		return p[1:]
	else:
		return p

def prefix_mins(a, drop_zero=False):
	p = [float('inf')]
	for x in a:
		p.append(min(p[-1], x))
	if drop_zero:
		return p[1:]
	else:
		return p

class DSU:

	def __init__(self, nodes):
		self.p = [i for i in range(nodes)]
		self.r = [0 for i in range(nodes)]
		self.s = [1 for i in range(nodes)]

	def get(self, u):
		if u != self.p[u]:
			self.p[u] = self.get(self.p[u])
		return self.p[u]

	def union(self, u, v):
		u = self.get(u)
		v = self.get(v)
		if u != v:
			if self.r[u] > self.r[v]:
				(u, v) = (v, u)
			self.p[u] = v
			if self.r[u] == self.r[v]:
				self.r[v] += 1
			self.s[v] += self.s[u]

def solve_a():
	(n, m) = get_ints()
	dsu = DSU(n + m)
	for employee in range(n):
		dat = get_ints()
		for x in dat[1:]:
			dsu.union(employee, n + x - 1)
	components = 0
	for i in range(n + m):
		if dsu.get(i) == i and (i < n or dsu.s[dsu.get(i)] > 1):
			components += 1
	if max(dsu.s) == 1:
		return components
	else:
		return components - 1
print(solve_a())


import sys
import itertools
import collections

def rs(x=''):
	return sys.stdin.readline().strip() if len(x) == 0 else input(x).strip()

def ri(x=''):
	return int(rs(x))

def rm(x=''):
	return map(str, rs(x).split())

def rl(x=''):
	return rs(x).split()

def rmi(x=''):
	return map(int, rs(x).split())

def rli(x=''):
	return [int(x) for x in rs(x).split()]

def println(val):
	sys.stdout.write(str(val) + '\n')

def dfs(graph, alpha, visited):
	n = len(graph)
	stack = [alpha]
	while stack:
		v = stack[-1]
		if not visited[v]:
			visited[v] = True
			for u in graph[v]:
				if not visited[u]:
					stack.append(u)
		else:
			stack.pop()

def solve(testCase):
	(n, m) = rmi()
	iThPersonKnownLanguages = [[0 for _ in range(m)] for _ in range(n)]
	areZeros = 0
	for i in range(n):
		(k, *vals) = rli()
		if k == 0:
			areZeros += 1
		for j in vals:
			iThPersonKnownLanguages[i][j - 1] = 1
	graph = [[] for _ in range(n)]
	if areZeros == n:
		print(n)
		return
	for i in range(n):
		for j in range(n):
			for k in range(m):
				if iThPersonKnownLanguages[i][k] > 0 and iThPersonKnownLanguages[j][k] > 0:
					graph[i] += [j]
					graph[j] += [i]
	ans = -1
	vis = [False] * n
	for i in range(n):
		if not vis[i]:
			ans += 1
			dfs(graph, i, vis)
	print(ans)
for _ in range(ri() if 0 else 1):
	solve(_ + 1)