「C.E.L.U-01」族谱树
题目链接
题目背景
小 Soup 正在翻看他们家的族谱,他们家的族谱构成了一棵树。小 Soup 发现,由于年代久远,他们家族中的一些分支已经绝迹,他对此十分好奇。
题目描述
小 Soup 给你他们家的族谱树,想要问你在这棵树中所有第 k k k 层的孩子(树中深度为 k k k 的点,根节点的深度为 1 1 1 ,根节点编号为 1 1 1 )的 最近公共祖先 \text{最近公共祖先} 最近公共祖先 是谁。
输入格式
第一行两个整数
n
,
m
n,m
n,m。
第二行
n
n
n 个整数,其中第
i
i
i 个整数为
f
i
f_i
fi,表示
i
i
i 的父亲为
f
i
f_i
fi,请注意,
1
1
1 的
f
i
f_i
fi 固定为
0
0
0。
接下来
m
m
m 行,每行一个整数
k
k
k,代表小 Soup 的询问。
输出格式
对于每个小 Soup 的询问,输出一个整数,即所有深度为 k k k 的点的 最近公共祖先 \text{最近公共祖先} 最近公共祖先。
样例 #1
样例输入 #1
8 3
0 1 1 2 2 3 4 5
2
1
4
样例输出 #1
1
1
2
样例 #2
样例输入 #2
11 4
0 1 1 3 3 3 4 5 8 8 10
3
4
5
6
样例输出 #2
3
3
8
11
提示
样例解释1:
样例解释2:
数据保证存在深度为 k k k 的点
数据编号 n , m 特殊性质 1 ≤ 10 ╲ 2 ≤ 100 ╲ 3 ∼ 4 ≤ 1 0 3 ╲ 5 ≤ 3 × 1 0 5 树为一条链 6 ≤ 3 × 1 0 5 ╲ 7 ∼ 10 ≤ 3 × 1 0 6 ╲ 11 ∼ 12 ≤ 5 × 1 0 6 ╲ \begin{array}{|c|c|c|}数据编号&n,m&特殊性质\\1&\le10&\diagdown\\2&\le100&\diagdown\\3\sim4&\le10^3&\diagdown\\5&\le3\times10^5&树为一条链\\6&\le3\times10^5&\diagdown\\7\sim10&\le3\times10^6&\diagdown\\11\sim12&\le5\times10^6&\diagdown\end{array} 数据编号123∼4567∼1011∼12n,m≤10≤100≤103≤3×105≤3×105≤3×106≤5×106特殊性质╲╲╲树为一条链╲╲╲
对于 100 % 100\% 100% 的数据, n ≤ 5 × 1 0 6 , m ≤ n n\le5\times10^6,m\le n n≤5×106,m≤n。
温馨提示:此题较卡常,请注意大常数带来的影响以及时空复杂度。如果你被卡常了,可以试试使用快速读入。
思路涉及知识点——Tarjan算法、并查集
本人蒟蒻,浅谈自己的一些看法,如有不对之处,请各位大佬指正。
Tarjan
算法是一种离线算法,需要使用并查集记录下每个结点的祖先结点。为了方便遍历每个结点的直接孩子结点,本题我使用的是vector散列表(应该是这个叫法吧)。
首先需要对进行预处理,初始状态,每个并查集数组第
i
i
i 个结点的祖先是其本身。
然后,使用dfs深搜。过程中,计算出每个结点对应的深度(主要是本题需要回答的询问是每个相同深度结点的LCA)。
遍历当前结点的所有孩子结点,并更新当前结点所在深度的最近祖先结点。若首次找到当前深度,则将祖先结点更新为当前节点本身。再次找到这个深度时,更新祖先结点为并查集数组中记录的当前祖先的祖先结点。
此处有个注意点,更新当前结点的祖先结点应该放在深搜完当前结点之后。这样才可以做到该结点的所有孩子结点的最近祖先结点是当前结点,而不是当前结点的父亲结点。
并查集 find
方法中也需要更新所查 p
结点的最上祖先结点,减少后续查找深度。
题解代码
可能表达的不是很清楚,直接上代码看可能更直观些。由于本题卡常比较严重,此处用到了快读快写,要不然有一个测试点会 TLE
。
#include<bits/stdc++.h>
using namespace std;
const int N = 5e6 + 3;
int n, m, x;
// f 数组并查集
// deep 数组记录每个结点的深度
// ans 数组记录答案
int f[N], deep[N] = {0}, ans[N];
vector<int> edge[N];
inline int read() // 快读就不用说了吧
{
int s = 0;
char c = getchar();
while (!isdigit(c))
c = getchar();
while (isdigit(c))
s = (s << 3) + (s << 1) + (c ^ 48), c = getchar();
return s;
}
inline void out(int N){
if (N < 0){
putchar('-');
N = -N;
}
if (N >= 10){
out(N / 10);
}
putchar(N % 10 + '0');
}
int find(int p)
{
return f[p] == p ? p : f[p] = find(f[p]); // 在查找父节点过程更新到最上方父节点
}
void dfs(int y, int fa)
{
deep[y] = deep[fa] + 1; // 孩子结点的深度在父节点基础上 + 1
for(int i : edge[y])
{
dfs(i, y);
f[i] = y; // 更新父子结点关系
}
ans[deep[y]] = ans[deep[y]] ? find(ans[deep[y]]) : y;
}
int main()
{
n = read();
m = read();
for(register int i(1); i <= n; ++i)
{
f[i] = i; // 初始化并查集
edge[read()].push_back(i);
}
dfs(1, 0);
while (m--)
{
out(ans[read()]);
putchar('\n');
}
return 0;
}