import java.io.PrintWriter

fun next() = readLine()!!
fun nextInt() = next().toInt()
fun nextLong() = next().toLong()
fun nextDouble() = next().toDouble()
fun nextList() = next().split(" ")
fun nextIntList() = nextList().map(String::toInt)
fun nextLongList() = nextList().map(String::toLong)
fun nextDoubleList() = nextList().map(String::toDouble)

val pw = PrintWriter(System.out)
fun print(s: Any = "") = pw.print(s)
fun println(s: Any = "") = pw.println(s)

fun main(args: Array<String>) {
    main()
    pw.flush()
}

fun main() {
    val N = nextInt()
    val inputs = Array(N) {
        val A = nextInt()
        Array(A) {
            val (x, y) = nextIntList()
            intArrayOf(x, y)
        }
    }
    val ans = f(0, inputs, IntArray(N + 1) { -1 }, 0)
    println(ans)
}

fun f(n: Int, data: Array<Array<IntArray>>, results: IntArray, cnt: Int): Int {
    if (n >= data.size) return cnt
    val type = results[n + 1]
    return when (type) {
        0 -> {
            f(n + 1, data, results, cnt)
        }
        1 -> {
            val updated_results = update(data[n], results)
            if (updated_results == null) {
                0
            } else {
                f(n + 1, data, updated_results, cnt + 1)
            }
        }
        else -> {
            val skip_results = results.clone()
            skip_results[n + 1] = 0
            val updated_results = update(data[n], results)
            if (updated_results == null) {
                f(n + 1, data, skip_results, cnt)
            } else {
                updated_results[n + 1] = 1
                Math.max(f(n + 1, data, updated_results, cnt + 1), f(n + 1, data, skip_results, cnt))
            }
        }
    }
}

fun update(data: Array<IntArray>, results: IntArray): IntArray? {
    val new_results = results.clone()
    for (d in data) {
        val (x, y) = d
        if (new_results[x] != -1 && new_results[x] != y) return null
        new_results[x] = y
    }
    return new_results
}