#include <bits/stdc++.h>
using namespace std;
const int INF = 1 << 29;
vector<vector<int> > adj;
int n, sex[100000];
struct Edge {
  int u, v;
  Edge() {}
  Edge(int U, int V) : u(U), v(V) {}
};
struct Result {
  int num, good;
  Result() : num(0), good(0) {}
  Result(int N, int G) : num(N), good(G) {}
  Result &operator+=(const Result &r) {
    num += r.num;
    good += r.good;
    return *this;
  }
  Result &operator-=(const Result &r) {
    num -= r.num;
    good -= r.good;
    return *this;
  }
  Result operator+(const Result &r) const {
    Result s = *this;
    return s += r;
  }
  Result operator-(const Result &r) const {
    Result s = *this;
    return s -= r;
  }
  bool operator<(const Result &r) const {
    if (num != r.num) return num < r.num;
    return good < r.good;
  }
};
Result go0(int u, int parent);
Result go1(int u, int parent);
pair<vector<vector<int> >, vector<Edge> > decompose() {
  vector<vector<int> > comps;
  vector<bool> visited(n);
  vector<Edge> edges;
  for (int i = 0; i < (n); i++)
    if (!visited[i]) {
      vector<int> comp;
      Edge e;
      queue<pair<int, int> > qu;
      qu.push(make_pair(i, -1));
      visited[i] = true;
      while (!qu.empty()) {
        pair<int, int> a = qu.front();
        qu.pop();
        int u = a.first, bef = a.second;
        comp.push_back(u);
        for (int j = 0; j < (adj[u].size()); j++) {
          int v = adj[u][j];
          if (v != bef) {
            if (!visited[v]) {
              qu.push(make_pair(v, u));
              visited[v] = true;
            } else
              e = Edge(u, v);
          }
        }
      }
      comps.push_back(comp);
      edges.push_back(e);
    }
  return make_pair(comps, edges);
}
Result dp[2][100000];
int match[100000];
short path[2][100000];
Result go0(int u, int parent) {
  if (~dp[0][u].num) return dp[0][u];
  if (adj[u].size() == 0 || (adj[u].size() == 1 && adj[u][0] == parent)) {
    return dp[0][u] = Result(0, 0);
  }
  Result ans, ans2;
  int x = -1;
  for (int i = 0; i < (adj[u].size()); i++) {
    int v = adj[u][i];
    if (v != parent) {
      Result tmp0 = go0(v, u);
      Result tmp1 = go1(v, u);
      ans += max(tmp0, tmp1);
      if (tmp1 < tmp0) {
        path[0][v] = 0;
      } else {
        path[0][v] = 1;
      }
      Result dif =
          Result(1, sex[u] == sex[v] ? 0 : 1) + min(tmp1 - tmp0, Result(0, 0));
      if (x == -1 || ans2 < dif) {
        ans2 = dif;
        x = v;
      }
    }
  }
  path[0][x] = 1;
  match[u] = x;
  return dp[0][u] = ans + ans2;
}
Result go1(int u, int parent) {
  if (~dp[1][u].num) return dp[1][u];
  if (adj[u].size() == 0 || (adj[u].size() == 1 && adj[u][0] == parent)) {
    return dp[1][u] = Result(0, 0);
  }
  Result ans;
  for (int i = 0; i < (adj[u].size()); i++) {
    int v = adj[u][i];
    if (v != parent) {
      Result tmp0 = go0(v, u);
      Result tmp1 = go1(v, u);
      ans += max(tmp0, tmp1);
      if (tmp1 < tmp0) {
        path[1][v] = 0;
      } else {
        path[1][v] = 1;
      }
    }
  }
  return dp[1][u] = ans;
}
void constructMatchings(int u, int parent, int type, vector<Edge> &matches) {
  if (type == 0) {
    matches.push_back(Edge(u, match[u]));
  }
  for (int i = 0; i < (adj[u].size()); i++) {
    int v = adj[u][i];
    if (v != parent) {
      if (path[type][v] == 0) {
        constructMatchings(v, u, 0, matches);
      } else {
        constructMatchings(v, u, 1, matches);
      }
    }
  }
}
void deleteNode(int u) {
  for (int i = 0; i < (adj[u].size()); i++) {
    int v = adj[u][i];
    adj[v].erase(find(adj[v].begin(), adj[v].end(), u));
  }
  adj[u].clear();
}
void deleteEdge(const Edge &e) {
  int u = e.u, v = e.v;
  adj[u].erase(find(adj[u].begin(), adj[u].end(), v));
  adj[v].erase(find(adj[v].begin(), adj[v].end(), u));
}
pair<Result, vector<Edge> > solve(const vector<int> &V, const Edge &e) {
  int u = e.u, v = e.v;
  deleteEdge(e);
  Result tmp0 = go0(u, -1);
  for (int k = 0; k < (2); k++)
    for (int i = 0; i < (V.size()); i++) dp[k][V[i]] = Result(-1, 0);
  Result tmp1 = go1(u, -1);
  Result r1;
  vector<Edge> matches;
  if (tmp0 < tmp1) {
    r1 = tmp1;
    constructMatchings(u, -1, 1, matches);
  } else {
    r1 = tmp0;
    constructMatchings(u, -1, 0, matches);
  }
  vector<int> cand = adj[v];
  vector<pair<int, int> > res;
  deleteNode(v);
  for (int k = 0; k < (2); k++)
    for (int i = 0; i < (V.size()); i++) dp[k][V[i]] = Result(-1, 0);
  Result r2 = go1(u, -1) + Result(1, sex[u] == sex[v] ? 0 : 1);
  for (int i = 0; i < (cand.size()); i++) {
    int x = cand[i];
    if (dp[1][x].num == -1) {
      Result t0 = go0(x, -1), t1 = go1(x, -1);
      r2 += max(t0, t1);
      if (t0 < t1)
        res.push_back(make_pair(x, 1));
      else
        res.push_back(make_pair(x, 0));
    }
  }
  if (r1 < r2) {
    matches.clear();
    constructMatchings(u, -1, 1, matches);
    for (int i = 0; i < (res.size()); i++) {
      constructMatchings(res[i].first, -1, res[i].second, matches);
    }
    matches.push_back(Edge(u, v));
    r1 = r2;
  }
  return make_pair(r1, matches);
}
int main() {
  scanf("%d", &n);
  adj.resize(n);
  for (int u = 0; u < (n); u++) {
    int v;
    scanf("%d%d", &v, sex + u);
    v--;
    adj[u].push_back(v);
    adj[v].push_back(u);
  }
  pair<vector<vector<int> >, vector<Edge> > res = decompose();
  vector<vector<int> > &comps = res.first;
  vector<Edge> &edges = res.second;
  for (int k = 0; k < (2); k++)
    for (int u = 0; u < (n); u++) dp[k][u] = Result(-1, 0);
  memset(match, -1, sizeof match);
  memset(path, -1, sizeof path);
  Result ans;
  vector<Edge> matches;
  for (int i = 0; i < (comps.size()); i++) {
    pair<Result, vector<Edge> > tmp = solve(comps[i], edges[i]);
    ans += tmp.first;
    matches.insert(matches.end(), tmp.second.begin(), tmp.second.end());
  }
  printf("%d %d\n", ans.num, ans.good);
  for (int i = 0; i < (matches.size()); i++)
    printf("%d %d\n", matches[i].u + 1, matches[i].v + 1);
  return 0;
}
