JZOJ3054 祖孙询问 题解

JZOJ3054 祖孙询问 题解


Description
已知一棵n个节点的有根树。有m个询问。每个询问给出了一对节点的编号x和y,询问x与y的祖孙关系。

Input
输入第一行包括一个整数n表示节点个数。
接下来n行每行一对整数对a和b表示a和b之间有连边。如果b是-1,那么a就是树的根。
第n+2行是一个整数m表示询问个数。
接下来m行,每行两个正整数x和y。

Output
对于每一个询问,输出1:如果x是y的祖先,输出2:如果y是x的祖先,否则输出0。

Sample Input
10
234 -1
12 234
13 234
14 234
15 234
16 234
17 234
18 234
19 234
233 19
5
234 233
233 12
233 13
233 15
233 19

Sample Output
1
0
0
0
2

Hint
对于100%的.n,m≤40000,每个节点的编号都不超过40000。

典型的一眼题。
两个解法:
1.各种LCA算法。如果u是v的祖先那么LCA(u, v) == u,反之LCA(u, v) == v说明v是u的祖先,如果LCA(u, v)既不等于u也不等于v,那么两个点没有关系。
解法1代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>

const int N = 4e4 + 3;

int root, n, m, u, v, tot = 0;
int st[N], to[N << 1], nx[N << 1], f[N][21], dep[N], buc[N];

inline int read()
{
	int x = 0, f = 0;
	char c = getchar();
	for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
	for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
	return f ? -x : x;
}
inline void add(int u, int v) { to[++tot] = v, nx[tot] = st[u], st[u] = tot; }
inline void swap(int &a, int &b) { int t = a; a = b, b = t; }

void dfs(int u)
{
	for (int i = st[u]; i; i = nx[i])
	{
		int v = to[i];
		if (v != f[u][0])
			f[v][0] = u, dep[v] = dep[u] + 1, dfs(v);
	}
}

int LCA(int u, int v)
{
	if (dep[v] > dep[u]) swap(u, v);
	for (int i = 20; i >= 0; i--)
		if (dep[f[u][i]] >= dep[v])
			u = f[u][i];
	if (u == v) return u;
	for (int i = 20; i >= 0; i--)
		if (f[u][i] != f[v][i])
			u = f[u][i], v = f[v][i];
	return f[u][0];
}

void init()
{
	n = read();
	for (int i = 1; i <= n; i++)
	{
		u = read(), v = read();
		if (v == -1) root = u; //记录根节点
		else
		{
			add(u, v), add(v, u);
			buc[u] = buc[v] = 1;
		}
	}
	f[root][0] = 0, dep[root] = 1, dfs(root);
	//采用倍增LCA
	for (int j = 1; j <= 20; j++)
		for (int i = 1; i <= 40000; i++)
			if (buc[i]) f[i][j] = f[f[i][j - 1]][j - 1];
}

void solve()
{
	m = read();
	while (m--)
	{
		u = read(), v = read();
		if (u == v || !buc[u] || !buc[v]) printf("0
"); //这个判断貌似是多余的
		else
		{
			int lca = LCA(u, v); //求出LCA
			if (lca == u) printf("1
");
			else if (lca == v) printf("2
");
			else printf("0
");
		}
	}
}

int main()
{
	init();
	solve();
	return 0;
}

2.dfs序。记录每个节点的dfs序和size,u的子树范围(dfsn[u]表示u的dfs序)是dfsn[u]~dfsn[u] + size[u] - 1,如果dfsn[v]在这个范围内,那么u是v的祖先,反之亦然。
解法2代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>

const int N = 4e4 + 3;

int n, u, v, q, root, tot = 0, cnt = 0;
int st[N], to[N << 1], nx[N << 1], tid[N], siz[N];

inline int read()
{
	int x = 0, f = 0;
	char c = getchar();
	for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
	for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
	return f ? -x : x;
}
inline void add(int u, int v) { to[++tot] = v, nx[tot] = st[u], st[u] = tot; }

void dfs(int u, int from)
{
	tid[u] = ++cnt, siz[u] = 1; //记录dfs序和size
	for (int i = st[u]; i; i = nx[i])
	{
		int v = to[i];
		if (v != from)
		{
			dfs(v, u);
			siz[u] += siz[v]; 累加子树的size
		}
	}
}

void init()
{
	n = read();
	for (int i = 1; i <= n; i++)
	{
		u = read(), v = read();
		if (v == -1) root = u;
		else add(u, v), add(v, u);
	}
	dfs(root, 0);
}

void solve()
{
	q = read();
	while (q--)
	{
		u = read(), v = read();
		if (tid[u] <= tid[v] && tid[v] <= tid[u] + siz[u] - 1) printf("1
"); //u是v的祖先
		else if (tid[v] <= tid[u] && tid[u] <= tid[v] + siz[v] - 1) printf("2
"); //v是u的祖先
		else printf("0
"); //都不是
	}
}

int main()
{
	init();
	solve();
	return 0;
}

终于有那么一道可以做的题目了。

原文地址:https://www.cnblogs.com/zjlcnblogs/p/8551119.html