#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <queue>

using namespace std;

using Weight=int;
using DArray=vector<Weight>;
using DMatrix=vector<DArray>;

static const Weight INF=1<<29;

struct Edge {
    size_t src, dst;
    Weight weight;
    Edge() {}
    Edge(size_t src, size_t dst, Weight weight=1):
        src(src), dst(dst), weight(weight)
    {}
};

bool operator<(const Edge &e, const Edge &f) {
    if (e.weight != f.weight) {
        return e.weight > f.weight;
    } else {
        return e.src!=f.src? e.src<f.src : e.dst<f.dst;
    }
}

using Edges=vector<Edge>;
using Vertex=vector<Edge>;
using Graph=vector<Vertex>;

void join(Graph &g, size_t s, size_t d, Weight w=1) {
    // non-directed
    g[s].push_back(Edge(s, d, w));
    g[d].push_back(Edge(d, s, w));
}

void connect(Graph &g, size_t s, size_t d, Weight w=1) {
    // directed
    g[s].push_back(Edge(s, d, w));
}

const char *node_types[]={"root", "internal node", "leaf"};

size_t get_root(const Graph &g, vector<size_t> &ntype) {
    // stores the node type
    size_t V=g.size();
    vector<Weight> indeg(V), outdeg(V);
    for (size_t i=0; i<V; ++i)
        for (const Edge &e: g[i]) {
            indeg[e.dst] += e.weight;
            outdeg[e.src] += e.weight;
        }

    ntype.assign(V, 0);
    for (size_t i=0; i<V; ++i)
        if (indeg[i]) {
            if (!outdeg[i]) {
                ntype[i] = 2;
            } else {
                ntype[i] = 1;
            }
        }

    for (size_t i=0; i<V; ++i)
        if (!indeg[i])
            return i;

    return 0;
}

size_t get_depth(
    const Graph &g, vector<size_t> &depth, size_t root, size_t node=-1
) {
    // stores the depth from root for each of nodes below node
    depth.assign(g.size(), 0);

    if (!~node) node = root;

    queue<size_t> q; q.push(root);
    while (!q.empty()) {
        size_t v=q.front(); q.pop();
        for (const Edge &e: g[v]) {
            depth[e.dst] = depth[v] + 1;
            q.push(e.dst);
        }
    }

    return depth[node];
}

size_t get_height(
    const Graph &g, vector<size_t> &height, size_t root, size_t node=-1
) {
    // stores the height for each of nodes below node
    if (height.size() < g.size())
        height.assign(g.size(), 0);

    if (!~node) node = root;
    if (g[node].empty()) return 0;
    for (const Edge &e: g[node])
        height[node] = max(height[node], get_height(g, height, root, e.dst)+1);

    return height[node];
}

size_t get_sibling(const Graph &g, size_t node, size_t parent) {
    if (!~parent) return -1;

    for (const Edge &e: g[parent])
        if (e.dst != node)
            return e.dst;

    return -1;
}

int main() {
    size_t n;
    scanf("%zu", &n);

    Graph g(n);
    vector<size_t> parent(n, -1);
    for (size_t i=0; i<n; ++i) {
        size_t p, lch, rch;
        scanf("%zu %zu %zu", &p, &lch, &rch);
        if (~lch) {
            connect(g, p, lch);
            parent[lch] = p;
        }
        if (~rch) {
            connect(g, p, rch);
            parent[rch] = p;
        }
    }

    vector<size_t> depth, height, ntype;

    size_t r=get_root(g, ntype);
    get_depth(g, depth, r);
    get_height(g, height, r);

    for (size_t i=0; i<n; ++i) {
        printf("node %zu: ", i);
        printf("parent = %zd, ", parent[i]);
        printf("sibling = %zd, ", get_sibling(g, i, parent[i]));
        printf("degree = %zu, ", g[i].size());
        printf("depth = %zu, ", depth[i]);
        printf("height = %zu, ", height[i]);
        printf("%s\n", node_types[ntype[i]]);
    }

    return 0;
}

Measured memory_bytes: 2760