#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <numeric>
#include <queue>
#include <stack>
#include <map> 
#include <set>
#include <string>
#include <functional>
#include <list>
#include <random>
#include <time.h>
#include <iomanip>
#include <assert.h>
#include <numeric>
#include <sstream>
#include <iterator>
#define BIT(nr) (1UL << (nr))
#define int long long
//#define ll long long
#define double long double
#define mod 1000000007
#define MAXN (int)1e+5 * 2+1
#define LL_MAX 9223372036854775807	//ない環境用
#define LL_HALFMAX 9223372036854775807 / 2	//ない環境用
#define MIN -(9223372036854775807 / 2)
#define REP(i,a,n) for(int i=(a); i<(int)(n); i++)
#define rep(i,n) REP(i,0,n)
#define FOR(it,c) for(__typeof((c).begin()) it=(c).begin(); it!=(c).end(); ++it)
#define ALLOF(c) (c).begin(), (c).end()
#define REPS(i,x) for(int i=1;i<=(int)(x);i++)
#define RREP(i,x) for(int i=((int)(x)-1);i>=0;i--)
#define RREPS(i,x) for(int i=((int)(x));i>0;i--)
#define repl(i,a,b) for(int i=(int)(a);i<(int)(b);i++)
#define mp make_pair
template<typename T1, typename T2> inline void chmin(T1 & a, T2 b) { if (a > b) a = b; }
template<typename T1, typename T2> inline void chmax(T1& a, T2 b) { if (a < b) a = b; }


using namespace std;

//デバッグ用カッコの有無
#ifdef DEBUG
template <class T>ostream &operator<<(ostream &o, const vector<T>&v)
{
	o << "{"; for (int i = 0; i<(int)v.size(); i++)o << (i>0 ? ", " : "") << v[i]; o << "}"; return o;
}
#endif // DEBUG

template <class T>ostream &operator<<(ostream &o, const vector<T>&v)
{
	for (int i = 0; i<(int)v.size(); i++)o << (i>0 ? " " : "") << v[i]; return o;
}

int dx[4] = { 0, 1, 0, -1 }; // x軸方向への変位
int dy[4] = { 1, 0, -1, 0 }; // y軸方向への変位

int dxp[4] = { 0, 1 }; // x軸方向への変位(正のみ)
int dyp[4] = { 1, 0 }; // y軸方向への変位(負のみ)

using Weight = int;
using Flow = int;
struct Edge {
	int src, dst;

	// libalgo のものに追加、メンバを追加するだけなので互換性は崩さないはず、逆辺のG[e.dstの]インデックスを保持
	int rev;
	Weight weight;
	Flow cap;
	Edge() : src(0), dst(0), weight(0) {}
	Edge(int s, int d, Weight w) : src(s), dst(d), weight(w) {}
};
using Edges = std::vector<Edge>;
using Graph = std::vector<Edges>;
using Array = std::vector<Weight>;
using Matrix = std::vector<Array>;

void add_edge(Graph& g, int a, int b, Weight w = 1) {
	g[a].emplace_back(a, b, w);
	g[b].emplace_back(b, a, w);
}
void add_arc(Graph& g, int a, int b, Weight w = 1) { g[a].emplace_back(a, b, w); }

// 辺がメンバに src と dst を持つ隣接リスト表記のグラフをダンプ(https://hello-world-494ec.firebaseapp.com/) に投げることを想定
template <typename T>
void dump_graph(T G) {
	int V = G.size();
	int E = 0;
	ostringstream os;

	for (auto es : G) {
		for (auto e : es) {
			E++;
			os << e.src << " " << e.dst << "\n";
		}
	}
	cout << V << " " << E << "\n";
	cout << os.str() << "\n";
}

// グリッドからグラフを構築
// @pre: gはノード数H*Wのグラフ
void create_from_grid(Graph& g, int h, int w, vector<string>& mapData, char wall) {
	//グラフ構築 O(HW)
	rep(y, h) {
		rep(x, w) {
			if (mapData[y][x] == wall) {
				continue;
			}

			int id = y * w + x;
			//右と下(変位が正)のみ見る(辺の重複を回避するため)
			rep(i, 2) {
				int nx = x + dxp[i];
				int ny = y + dyp[i];
				int nid = ny * w + nx;
				if (nx < 0 || nx >= w) {
					continue;
				}
				if (ny < 0 || ny >= h) {
					continue;
				}
				if (mapData[ny][nx] != wall) {
					add_edge(g, id, nid);
				}
			}
		}
	}
}

// グリッドにおいて座標をグラフのノード番号に変換する
int point_to_node_num(int x, int y, int W) {
	return y * W + x;
}

struct uf_tree {
	std::vector<int> parent;
	int __size;
	uf_tree(int size_) : parent(size_, -1), __size(size_) {}
	void unite(int x, int y) {
		if ((x = find(x)) != (y = find(y))) {
			if (parent[y] < parent[x]) std::swap(x, y);
			parent[x] += parent[y];
			parent[y] = x;
			__size--;
		}
	}
	bool is_same(int x, int y) { return find(x) == find(y); }
	int find(int x) { return parent[x] < 0 ? x : parent[x] = find(parent[x]); }
	int size(int x) { return -parent[find(x)]; }
	int size() { return __size; }
};




/*
行列積と行列累乗
行列積
vector<vector<T>> matrixMultiplies(vector<vector<T>> l, vector<vector<T>> r, F plus = plus<T>(), G multiple = multiplies<T>(), T eplus = 0LL)
行列累乗
vector<vector<T>> matrixPower(vector<vector<T>> m, int n, F plus = std::plus<T>(), G multiple = multiplies<T>(), T eplus = 0LL, T emultiple = 1LL)
T:			考える集合(競プロにおいてはたぶんほぼ整数)
l:			左からかける行列
r:			右からかける行列
plus:		加法演算
multiple:	乗法演算
eplus:		加法の単位元
emultiple:	乗法の単位元
*/
template<typename T = long long, typename F = decltype(std::plus<T>()), typename G = decltype(multiplies<T>())>
vector<vector<T>> matrixMultiplies(vector<vector<T>> l, vector<vector<T>> r, F plus = plus<T>(), G multiple = multiplies<T>(), T eplus = 0LL) {
	int rx = r[0].size();
	int ry = r.size();
	vector<vector<T> > ret;

	for (int y = 0; y < ry; y++) {
		vector<T> add;
		for (int x = 0; x < rx; x++) {
			T cell = eplus;
			for (int i = 0; i < ry; i++) {
				T mul = multiple(l[y][i], r[i][x]);
				cell = plus(cell, mul);
			}
			add.push_back(cell);
		}
		ret.push_back(add);
	}
	return ret;
}

template<typename T = long long, typename F = decltype(std::plus<T>()), typename G = decltype(multiplies<T>())>
vector<vector<T>> matrixPower(vector<vector<T>> m, int n, F plus = std::plus<T>(), G multiple = multiplies<T>(), T eplus = 0LL, T emultiple = 1LL) {
	int k = m.size();
	if (n == 0) {
		vector<vector<T> > E;
		for (int i = 0; i < k; i++) {
			// 単位行列は対角成分を乗法単位元、非対角成分をゼロ元で埋める
			vector<T> v(k, eplus);
			v[i] = emultiple;
			E.push_back(v);
		}
		return E;
	}
	vector<vector<T>> ret = matrixPower(matrixMultiplies(m, m, plus, multiple, eplus), n / 2, plus, multiple, eplus, emultiple);
	if (n % 2 == 1) {
		ret = matrixMultiplies(m, ret, plus, multiple);
	}
	return ret;
}

// フロー系のアルゴリズム
// 最大流
/*
Ford-Fulkerson法(蟻本) O(F|E|)
F: 最大流量
E: 辺数
コンストラクタ引数でノード数nを受け取り初期化し、add_edge で辺と逆辺を追加していく
*/
class Ford_Fulkerson {
private:
	struct Edge {
		int src, dst;

		// libalgo のものに追加、メンバを追加するだけなので互換性は崩さないはず、逆辺のG[e.dstの]インデックスを保持
		int rev;
		int cap;
		Edge(int s, int d, int c, int r) : src(s), dst(d), cap(c), rev(r) {}
	};
	vector<vector<Edge> > G;
	vector<bool> used;
public:
	Ford_Fulkerson(int n) :
		G(n),
		used(n, false)
	{}

	void add_edge(int s, int d, int cap) {
		G[s].emplace_back(s, d, cap, G[d].size());
		G[d].emplace_back(d, s, 0, G[s].size() - 1);
	}

	int dfs(int v, int t, int f) {
		if (v == t) {
			return f;
		}
		used[v] = true;
		for (Edge& e : G[v]) {
			if (!used[e.dst] && e.cap > 0) {
				// 流せる辺があったら流す
				int d = dfs(e.dst, t, min(f, e.cap));
				if (d > 0) {
					// 辺の残り容量を減らす
					e.cap -= d;
					// 逆辺の容量を増やす
					G[e.dst][e.rev].cap += d;
					return d;
				}
			}
		}
		// t にたどり着けなかったら0
		return 0;
	}
	int max_flow(int s, int t) {
		int flow = 0;

		while (1) {
			for (int i = 0; i < used.size(); i++) {
				used[i] = false;
			}
			int f = dfs(s, t, LL_HALFMAX);
			if (f == 0) {
				return flow;
			}
			flow += f;
		}
	}

};

/*
Dinic法 From libalgo O(V^2 * E)
dinic::solve(s, t) : s -> t の最大流を求める
dinic;;flow[u][v] : 辺(u, v)の流量
*/
struct dinic {
	int n, s, t;
	std::vector<int> level, prog, que;
	std::vector<std::vector<Flow>> cap, flow;
	std::vector<std::vector<int>> g;
	Flow inf;
	dinic(const Graph &graph)
		: n(graph.size()),
		cap(n, std::vector<Flow>(n)),
		flow(n, std::vector<Flow>(n)),
		g(n, std::vector<int>()),
		inf(std::numeric_limits<Flow>::max() / 8) {
		for (int i = 0; i < n; i++) {
			for (auto &e : graph[i]) {
				int u = e.src, v = e.dst;
				Flow c = e.cap;
				cap[u][v] += c;
				cap[v][u] += c;
				flow[v][u] += c;
				g[u].push_back(v);
				g[v].push_back(u);
			}
		}
	}
	// 残りを求める
	inline Flow residue(int u, int v) { return cap[u][v] - flow[u][v]; }

	// 実際に最大流問題を解く
	Flow solve(int s_, int t_) {
		this->t = t_, this->s = s_;
		que.resize(n + 1);
		Flow res = 0;
		// levelize() == false: bfs で s から t に到達できなかった
		while (levelize()) {
			prog.assign(n, 0);
			res += augment(s, inf);
		}
		return res;
	}
	// bfs でレベルグラフをつくる
	bool levelize() {
		int l = 0, r = 0;
		level.assign(n, -1);
		level[s] = 0;
		que[r++] = s;
		while (l != r) {
			int v = que[l++];
			if (v == t) break;
			for (const int &d : g[v]) {
				// まだレベルが決まっておらず、v -> dの辺に流せるならlevel[d] = level[v] + 1
				if (level[d] == -1 && residue(v, d) != 0) {
					level[d] = level[v] + 1;
					que[r++] = d;
				}
			}
		}
		// t に到達できるなら true を返す
		return level[t] != -1;
	}
	// dfs で実際に流してみる
	Flow augment(int v, Flow lim) {
		Flow res = 0;
		if (v == t) return lim;
		// prog[v]: dfs において、vを展開する際、vの子の何番目まで展開したかを覚えておく
		for (int &i = prog[v]; i < (int)g[v].size(); i++) {
			const int &d = g[v][i];
			// v -> d に流せない or v(流す側) の方がレベルが大きい(=深い)場合NG
			if (residue(v, d) == 0 || level[v] >= level[d]) continue;
			// 流せるなら、流せるだけ流す
			const Flow aug = augment(d, std::min(lim, residue(v, d)));
			flow[v][d] += aug;
			flow[d][v] -= aug;
			res += aug;
			lim -= aug;
			// ノードvに来ている流量を使い切ったら終わり
			if (lim == 0) break;
		}
		return res;
	}
};

/*
Primal-Dual法(蟻本版 / ベルマンフォード)
*/

class Primal_Dual_BellmanFord {
	using Cost = int;
	struct Edge {
		int src, dst;

		// libalgo のものに追加、メンバを追加するだけなので互換性は崩さないはず、逆辺のG[e.dstの]インデックスを保持
		int rev;
		Cost cost;
		Flow cap;
		Edge(int s, int d, int aRev, Cost aCost, Flow aCap) : src(s), dst(d), rev(aRev), cost(aCost), cap(aCap) {}
	};

	int V;							//頂点数
	vector<vector<Edge>> G;			// 隣接リスト
	vector<int> dist;				// 最短距離
	vector<int> prevv;				// 直前の頂点
	vector<int> preve;				// 直前の辺
	const int INF;

public:
	// 頂点数 n を引数にとって初期化
	Primal_Dual_BellmanFord(int n) :
		V(n),
		G(n),
		dist(n, 0),
		prevv(n, 0),
		preve(n, 0),
		INF(std::numeric_limits<int>::max() / 8) {}
	void add_edge(int src, int dst, int cap, int cost) {
		// cost は weight に入れる
		G[src].emplace_back(src, dst, G[dst].size(), cost, cap);
		G[dst].emplace_back(dst, src, G[src].size() - 1, -cost, 0);
	}

	int min_cost_flow(int s, int t, int f) {
		int res = 0;
		while (f > 0) {
			// ベルマンフォードによりs-t最短路をもとめる
			dist.assign(V, INF);
			dist[s] = 0;
			bool update = true;
			while (update) {
				update = false;
				for (int v = 0; v < V; v++) {
					if (dist[v] == INF) continue;
					for (int i = 0; i < G[v].size(); i++) {
						Edge& e = G[v][i];
						if (e.cap > 0 && dist[e.dst] > dist[v] + e.cost) {
							dist[e.dst] = dist[v] + e.cost;
							prevv[e.dst] = v;
							preve[e.dst] = i;
							update = true;
						}
					}
				}
			}

			// これ以上流せない
			if (dist[t] == INF) {
				return -1;
			}

			// 復元したs-t最短路に沿って流せるだけ流す
			int d = f;
			// 尻からprevvを辿っていき、流せる量を求める
			for (int v = t; v != s; v = prevv[v]) {
				// 一つ手前に戻るための辺
				Edge &e = G[prevv[v]][preve[v]];
				chmin(d, e.cap);
			}

			f -= d;

			// ここでの dist はコスト和なので、それに流す量をかけると今回見つけた最短パスに流すコストとなる。
			res += d * dist[t];
			for (int v = t; v != s; v = prevv[v]) {
				Edge &e = G[prevv[v]][preve[v]];
				e.cap -= d;
				G[v][e.rev].cap += d;
			}
		}
		return res;
	}
};

/*
ダイクストラ版 Primal-Dual
出典: https://ei1333.github.io/luzhiled/snippets/graph/primal-dual.html
*/

template< typename flow_t, typename cost_t >
struct PrimalDual {
	const cost_t INF;

	struct edge {
		int to;
		flow_t cap;
		cost_t cost;
		int rev;
		bool isrev;
		edge(int aTo, flow_t aCap, cost_t aCost, int aRev, bool aIsRev) : to(aTo), cap(aCap), cost(aCost), rev(aRev), isrev(aIsRev) {}
	};
	vector< vector< edge > > graph;
	vector< cost_t > potential, min_cost;
	vector< int > prevv, preve;

	PrimalDual(int V) : graph(V), INF(numeric_limits< cost_t >::max()) {}

	void add_edge(int from, int to, flow_t cap, cost_t cost) {
		graph[from].emplace_back(to, cap, cost, (int)graph[to].size(), false);
		graph[to].emplace_back(from, 0, -cost, (int)graph[from].size() - 1, true);
	}

	cost_t min_cost_flow(int s, int t, flow_t f) {
		int V = (int)graph.size();
		cost_t ret = 0;
		using Pi = pair< cost_t, int >;
		priority_queue< Pi, vector< Pi >, greater< Pi > > que;
		potential.assign(V, 0);
		preve.assign(V, -1);
		prevv.assign(V, -1);

		while (f > 0) {
			min_cost.assign(V, INF);
			que.emplace(0, s);
			min_cost[s] = 0;
			while (!que.empty()) {
				Pi p = que.top();
				que.pop();
				if (min_cost[p.second] < p.first) continue;
				for (int i = 0; i < graph[p.second].size(); i++) {
					edge &e = graph[p.second][i];
					cost_t nextCost = min_cost[p.second] + e.cost + potential[p.second] - potential[e.to];
					if (e.cap > 0 && min_cost[e.to] > nextCost) {
						min_cost[e.to] = nextCost;
						prevv[e.to] = p.second, preve[e.to] = i;
						que.emplace(min_cost[e.to], e.to);
					}
				}
			}
			if (min_cost[t] == INF) return -1;
			for (int v = 0; v < V; v++) potential[v] += min_cost[v];
			flow_t addflow = f;
			for (int v = t; v != s; v = prevv[v]) {
				addflow = min(addflow, graph[prevv[v]][preve[v]].cap);
			}
			f -= addflow;
			ret += addflow * potential[t];
			for (int v = t; v != s; v = prevv[v]) {
				edge &e = graph[prevv[v]][preve[v]];
				e.cap -= addflow;
				graph[v][e.rev].cap += addflow;
			}
		}
		return ret;
	}

	void output() {
		for (int i = 0; i < graph.size(); i++) {
			for (auto &e : graph[i]) {
				if (e.isrev) continue;
				auto &rev_e = graph[e.to][e.rev];
				cout << i << "->" << e.to << " (flow: " << rev_e.cap << "/" << rev_e.cap + e.cap << ")" << endl;
			}
		}
	}
};

class lca {
public:
	int n, segn;
	vector<int> path;		// 蟻本の vs、オイラーツアーを保持
	vector<int> depth;		// 蟻本の depth、path[i] であるノードの深さを保持
	vector<int> in_order;	// 蟻本の id、ノードiがオイラーツアーで最初に出てくるインデックスを保持
	vector<pair<int, int>> dat;
	const std::pair<int, int> INF = std::make_pair(1000000000, 1000000000);

	lca(const Graph& g, int root) : n(g.size()), path(n*2-1), depth(n*2-1), in_order(n) {
		int k = 0;
		dfs(g, root, -1, 0, k);

		// セグ木を構築、持つのはpair(depth, index) => depth が最小となる index がわかる 
		for (segn = 1; segn < n * 2 - 1; segn <<= 1);

		dat.assign(segn * 2, INF);
		for (int i = 0; i < (int)depth.size(); ++i) dat[segn + i] = std::make_pair(depth[i], i);
		for (int i = segn - 1; i >= 1; --i) dat[i] = min(dat[i * 2], dat[i * 2 + 1]);
	}

	int get(int u, int v) const {
		int l = std::min(in_order[u], in_order[v]);
		int r = std::max(in_order[u], in_order[v]) + 1;
		return path[range_min(1, segn, l, r).second];
	}

	void dfs(const Graph& g, int v, int p, int d, int& k) {
		// k: オイラーツアーの何番目かを保持する変数
		in_order[v] = k;
		path[k] = v;
		depth[k++] = d;
		for (auto &e : g[v]) {
			if (e.dst != p) {
				dfs(g, e.dst, v, d + 1, k);
				// ここに来た時はノードvの子であるe.dstを展開し終わってvに戻ってきたときなので、再度 path と depth に記録する
				path[k] = v;
				depth[k++] = d;
			}
		}
	}

	pair<int, int> range_min(int v, int w, int l, int r) const {
		if (r <= l || w == 0) return INF;
		if (r - l == w) return dat[v];

		int m = w / 2;
		auto rmin = range_min(v * 2, m, l, std::min(r, m));
		auto lmin = range_min(v * 2 + 1, m, std::max(0LL, l - m), r - m);

		return min(rmin, lmin);
	}
};

void solve(){

}

void solve_TLE(ostringstream& aout, long long N, std::vector<long long> A) {

}

signed main() {
	int n;
	cin >> n;

	Graph G(n);

	rep(i, n) {
		int k;
		cin >> k;
		rep(j, k) {
			int c;
			cin >> c;
			add_edge(G, i, c);
		}
	}

	lca Lca(G, 0);

	int q;
	cin >> q;

	rep(i, q) {
		int u, v;
		cin >> u >> v;
		cout << Lca.get(u, v) << "\n";
	}
	return 0;
}



Measured memory_bytes: 30460