📅  最后修改于: 2023-12-03 15:10:46.500000             🧑  作者: Mango
在树结构中,MEX可以定义为缺失的最小非负整数。给定一颗树,我们需要找到每个子树的MEX值。
本文将会介绍使用深度优先搜索和树上前缀和算法来解决这个问题。同时,我们也会提供相关的算法C++代码。
我们可以使用深度优先搜索算法来遍历树(先根遍历),并查找每个子树的MEX值。
对于每个节点,我们需要遍历它的所有子节点,并保存它们的MEX值。然后,在计算该节点的MEX值时,我们可以根据其子节点的MEX值,来得到其本身的MEX值。具体的计算方法见下图:
我们可以使用一个数组 mx[]
来记录每个节点的MEX值。具体来说,对于节点 u
,我们先遍历其所有子节点,并递归计算每个子节点的MEX值。然后,我们将子节点的MEX值插入到集合 s
中,并将 mx[u]
的值初始化为 s
中的最小未出现的非负整数。最后,我们返回 s
作为结果。
下面是深度优先搜索算法的C++代码:
int mx[N];
bool vis[N];
vector<int> adj[N];
int dfs(int u){
vis[u] = true;
vector<int> s;
for(int v : adj[u]){
if(!vis[v]){
s.emplace_back(dfs(v));
}
}
sort(s.begin(), s.end());
s.erase(unique(s.begin(), s.end()), s.end());
int i = 0;
while(i < s.size() && s[i] == i) ++i;
return mx[u] = i;
}
在上述代码中,我们使用一个 vis
数组来记录每个节点是否被遍历过。在遍历时,我们只对未被遍历过的节点进行处理。同时,为了方便起见,我们使用了C++ STL中的 vector
容器来管理集合 s
。具体来说,我们可以使用 s.emplace_back(x)
在末尾插入一个元素 x
,使用 s.erase(it1, it2)
删除区间 [it1, it2)
中的元素,并使用 sort(s.begin(), s.end())
将容器中的元素排序。
使用深度优先搜索算法可以为我们的问题解决提供有效的算法实现。然而,它的时间复杂度是 $O(N^2)$ 的($N$ 是树的节点数),因为对于每个节点,我们都需要回溯到其所有祖先节点来计算MEX值。如果树很大,这个算法的时间效率将很低。
在本节中,我们将介绍一种更快的算法,称为树上前缀和算法。这种算法基于以下性质:对于一个连通的子图,其MEX值等于集合 [0, k]
中未出现的最小整数,其中 k
是该子图中节点编号的最大值。
我们可以使用前缀和来计算每个节点子树的最大节点值。然后,我们可以使用深度优先搜索算法来计算每个节点的MEX值。具体来说,对于节点 u
,我们可以使用一个 cnt[]
数组来记录其子树中每个节点编号的出现次数。然后,我们可以使用前缀和算法来计算其子树的最大节点值 mx[u]
。最后,我们可以使用深度优先搜索算法来计算节点 u
的MEX值。
下面是树上前缀和算法的C++代码:
int mx[N], cnt[N];
vector<int> adj[N];
void dfs1(int u, int p){
cnt[u] = 1;
for(int v : adj[u]){
if(v != p){
dfs1(v, u);
cnt[u] += cnt[v];
mx[u] = max(mx[u], mx[v]);
}
}
mx[u] = max(mx[u], cnt[u]);
}
void dfs2(int u, int p){
vector<int> s;
for(int v : adj[u]){
if(v != p){
dfs2(v, u);
s.emplace_back(mx[v]);
}
}
sort(s.begin(), s.end());
s.erase(unique(s.begin(), s.end()), s.end());
int k = s.size();
for(int i = 0; i < k; ++i){
if(s[i] > i) break;
if(i == k-1){
mx[u] = max(mx[u], i+1);
break;
}
}
}
int main(){
int n, root;
cin >> n >> root;
for(int i = 1; i < n; ++i){
int u, v;
cin >> u >> v;
adj[u].emplace_back(v);
adj[v].emplace_back(u);
}
dfs1(root, 0);
dfs2(root, 0);
for(int i = 1; i <= n; ++i){
cout << mx[i]-1 << ' ';
}
cout << endl;
return 0;
}
在上述代码中,我们使用了两个深度优先搜索函数 dfs1()
和 dfs2()
。在 dfs1()
函数中,我们计算每个节点子树的最大节点值。具体来说,我们使用 cnt[]
数组来记录每个节点的子树大小(即子节点个数),并使用 mx[]
数组来记录子树中节点编号的最大值。当我们遍历到节点 u
的某个子节点 v
时,我们通过递归调用 dfs1(v, u)
来计算其子树大小和最大节点值,并使用 cnt[u] += cnt[v]
和 mx[u] = max(mx[u], mx[v])
来更新节点 u
的子树大小和最大节点值。
在计算完每个节点子树的最大节点值后,我们可以使用 dfs2()
函数来计算每个节点的MEX值。具体来说,对于节点 u
,我们先遍历其所有子节点,并递归计算每个子节点的MEX值。然后,我们将子节点的最大节点值 mx[v]
插入到集合 s
中,并使用前缀和算法来找到该子树的MEX值。最后,我们将节点 u
的MEX值更新为子树MEX值加1。
至此,我们已经介绍了两种算法来计算树中每个子树的MEX值。相比于深度优先搜索算法,树上前缀和算法具有更高的时间效率(时间复杂度为 $O(N\log N)$)和更简洁的代码(不需要使用集合数据结构)。