#![allow(unused, non_snake_case, dead_code, non_upper_case_globals)]
#![macro_use]

use std::cmp::*;
use std::collections::*;
use std::io::*;
use std::mem::*;
use std::num::*;
use std::slice::*;

fn main() {
    let (r, w, e) = (stdin(), stdout(), stderr());
    let mut io = IO::new(r.lock(), w.lock(), e.lock());
    // =============================================== //
    let n: usize = io.read();

    let mut t: Vec<Vec<(usize, usize)>> = vec![vec![]; n];
    for i in 0..n {
        let c: usize = io.read();
        for _ in 0..c {
            let (a, b): (usize, usize) = (io.read::<usize>() - 1, io.read());
            t[i].push((a, b));
        }
    }

    let mut gg: G = G {
        t: t,
        n: n,
        ans: -1,
    };
    gg.dfs(vec![]);
    io.write(gg.ans);
}
struct G {
    t: Vec<Vec<(usize, usize)>>,
    n: usize,
    ans: isize,
}
impl G {
    fn dfs(&mut self, honest: Vec<bool>) {
        if honest.len() == self.n {
            let ch = self.check(&honest);
            if ch {
                self.ans = max(
                    self.ans,
                    honest.iter().filter(|&x| *x == true).count() as isize,
                )
            }
            return;
        }
        let mut hc = honest.clone();
        let mut hc2 = honest.clone();
        hc.push(true);
        hc2.push(false);
        self.dfs(hc);
        self.dfs(hc2);
    }
    fn check(&mut self, honest: &Vec<bool>) -> bool {
        // honest: n の配列
        let mut ret = true;

        for i in 0..self.n {
            if honest[i] == false {
                continue;
            }
            for &j in self.t[i].iter() {
                if j.1 == 1 && !honest[j.0] {
                    ret = false;
                    return false;
                }
                if j.1 == 0 && honest[j.0] {
                    ret = false;
                    return false;
                }
            }
        }

        ret
    }
}

// IO
struct IO<R: Read, W: Write, E: Write> {
    Read: R,
    Write: BufWriter<W>,
    Error: BufWriter<E>,
}
impl<R: Read, W: Write, E: Write> IO<R, W, E> {
    fn new(r: R, w: W, e: E) -> IO<R, W, E> {
        IO {
            Read: r,
            Write: BufWriter::new(w),
            Error: BufWriter::new(e),
        }
    }
    fn read<T>(&mut self) -> T
    where
        T: std::str::FromStr,
    {
        let next: String = self
            .Read
            .by_ref()
            .bytes()
            .map(|c| c.unwrap() as char)
            .skip_while(|c| c.is_whitespace())
            .take_while(|c| !c.is_whitespace())
            .collect();
        next.parse().ok().unwrap()
    }
    fn chars(&mut self) -> Vec<char> {
        self.read::<String>().chars().collect()
    }
    fn readn<T: std::str::FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.read::<T>()).collect::<Vec<_>>()
    }
    fn readmat<T: std::str::FromStr>(&mut self, n: usize, m: usize) -> Vec<Vec<T>> {
        (0..n).map(|_| self.readn(m)).collect()
    }
    fn write<S: ToString>(&mut self, val: S) {
        self.Write.write(val.to_string().as_bytes());
    }
    fn writeln<S: ToString>(&mut self, val: S) {
        self.Write.write(val.to_string().as_bytes());
        self.Write.write(b"\n");
    }
    fn debug<S: std::fmt::Debug>(&mut self, val: S) {
        writeln!(self.Error, "(debug ) {:?}", &val);
    }
    fn debug2<S: std::fmt::Debug, T: std::fmt::Debug>(&mut self, val1: S, val2: T) {
        writeln!(self.Error, "(debug2) {:?} -> {:?}", val1, val2);
    }
}


Measured memory_bytes: 4352