import std.stdio, std.conv, std.array, std.string, std.algorithm, std.container, std.range, core.stdc.stdlib, std.math, std.typecons;
T[][] combinations(T)(T[] s, in int m) {   if (!m) return [[]];   if (s.empty) return [];   return s[1 .. $].combinations(m - 1).map!(x => s[0] ~ x).array ~ s[1 .. $].combinations(m); }

alias Appeal = Tuple!(int, "from", int, "to", bool, "isHonest");

void main() {
  auto N = readln.chomp.to!int;
  Appeal[] appeals;
  foreach(i; 0..N) {
    auto appeal_count = readln.chomp.to!int;
  
    foreach(o; 0..appeal_count) {
      auto xy = readln.split.to!(int[]);
      appeals ~= Appeal(i, xy[0] - 1, xy[1] == 1);
    }
  }

  int solve() {
    int answer;
    foreach(i; 1..N+1) {
      foreach(c; N.iota.array.combinations(i)) {
        auto honest_appeals = appeals.filter!(a => c.canFind(a.from));

        bool ok = true;
        bool[int] honests;
        foreach(ci; c) honests[ci] = true;

        foreach(n; 0..N) {
          auto list = honest_appeals.filter!(a => a.to == n).array;
          if (list.length == 0) continue;

          auto base = list[0].isHonest;
          if (!base && n in honests) {
            ok = false;
            break;
          }
          if (base) honests[n] = true;
          foreach(l; list.map!(a => a.isHonest)) {
            if (l != base) {
              ok = false;
              break;
            }
          }
          if (ok == false) break;
        }
        if (ok) {
          if (honests.length != i) continue;
          if (!c.all!(a => a in honests)) continue;
          
          answer = i;
          debug writeln(c);
          debug writeln(honest_appeals);
          break;
        }
      }
      if (answer != i) break;
    }
    return answer;
  }

  solve().writeln;
}
