#define _USE_MATH_DEFINES
#include<iostream>
#include<fstream>
#include<vector>
#include<string>
#include<algorithm>
#include<climits>
#include<numeric>
#include<cmath>
#include<cstring>
#include<deque>
#include<map>
#include<iomanip>

#define REP(i, n) for(int i = 0;i < n;i++)
#define REPR(i, n) for(int i = n;i >= 0;i--)
#define FOR(i, m, n) for(int i = m;i < n;i++)
#define FORR(i, m, n) for(int i = m;i >= n;i--)
#define VSORT(v) sort(v.begin(), v.end());
#define VE vector<int>
#define VEP vector<pair<int,int>>
#define llong long long
#define pb(a) push_back(a)
using namespace std;
const vector<VE> DIR{ {-1,0},{1,0},{0,-1},{0,1} };
const int MOD = 1000000007;
long long GCD(long long a, long long b) {
	if (b == 0) return a;
	else return GCD(b, a % b);
}
template<class T>
int dp2csv(const vector<vector<T>> & dp, const string fname);
int range_check(int val, int min, int max) {
	if (val < min) {
		return min;
	}
	if (val > max) {
		return max;
	}
	return val;
}
template<class T,class T1>
bool array_find(const T& array, const T1& dst) {
	auto result = find(array.first(), array.end(), dst);
	if (result == array.end()) {
		return false;
	}
	else {
		return true;
	}
}
int find_ans(int man, VE fact_check,VE now_assume,vector<VEP> report,int n) {
	if (man == n) {
		return accumulate(now_assume.begin(), now_assume.end(),0);
	}
	int retval = 0;
	if (fact_check[man] == -1) {
		int retval1 = 0, retval0 = 0;
		int is_fact = 1;
		REP(r, report[man].size()) {
			if ((fact_check[report[man][r].first] == 1 - report[man][r].second)) {
				is_fact = 0;
				break;
			}
		}
		VE new_fact_check = fact_check;
		VE new_now_assume = now_assume;
		if (is_fact) {
			new_now_assume[man] = 1;
			new_fact_check[man] = 1;
			REP(r, report[man].size()) {
				new_fact_check[report[man][r].first] = report[man][r].second;
			}
			retval1 = find_ans(man + 1, new_fact_check, new_now_assume, report, n);
		}
		now_assume[man] = 0;
		fact_check[man] = 0;
		retval0 = find_ans(man + 1, fact_check, now_assume, report, n);
		retval = max(retval0, retval1);
	}
	else if (fact_check[man] == 1) {
		int is_fact = 1;
		REP(r, report[man].size()) {
			if (fact_check[report[man][r].first] == 1 - report[man][r].second) {
				is_fact = 0;
				break;
			}
		}
		if (is_fact) {
			now_assume[man] = 1;
			REP(r, report[man].size()) {
				fact_check[report[man][r].first] = report[man][r].second;
			}
			retval = find_ans(man + 1, fact_check, now_assume, report, n);
		}
		else {
			return -1;
		}
	}
	else {
		now_assume[man] = 0;
		retval = find_ans(man + 1, fact_check, now_assume, report, n);
	}
	return retval;
}
int main() {
	int n;
	cin >> n;
	VE fact_check(n,-1);
	VE now_assume(n,-1);
	vector<VEP> report(n);
	int rep_n;
	REP(man, n) {
		cin >> rep_n;
		VEP reps(rep_n);
		REP(r, rep_n) {
			int a, b;
			cin >> a >> b;
			reps[r].first = a - 1;
			reps[r].second = b;
		}
		report[man]=reps;
	}
	int ans = find_ans(0, fact_check, now_assume, report, n);
	if (ans == -1) {
		cout << 0 << endl;
	}
	cout << ans << endl;
	return 0;
}
template<class T>
int dp2csv(const vector<vector<T>> & dp, const string fname) {
	ofstream ofs(fname);
	if (ofs.fail()) { cerr << "cannot open file. :" + fname << endl; return -1; }
	for (auto dp_row : dp) {
		for (auto dp_cell : dp_row) {
			ofs << dp_cell << ",";
		}
		ofs << endl;
	}
	ofs.close();
	return 0;
}


Measured memory_bytes: 256