📜  树上的不相交集联合|套装2(1)

📅  最后修改于: 2023-12-03 14:55:38.289000             🧑  作者: Mango

树上的不相交集合联合 | 套装2

简介

树上的不相交集合联合 | 套装2(DSU on Trees | Set 2)是一种基于树的数据结构,用于在树上维护多个不相交的子树。

该数据结构的主要用途是在树上处理多个查询,每个查询都询问一个子树内有多少个不同的元素。该数据结构可以在log_2(n)的时间复杂度内处理每个查询,其中n为树的节点数。

DSU on Trees | Set 2是DSU on Trees的扩展版本,使用了一种更加有效的数据结构来维护每个节点所在子树的信息。

实现原理

DSU on Trees | Set 2的核心思想是使用“合并并查集”的方式来维护每个节点所在子树的信息。这里的“合并并查集”指的是,对于树上的每个节点,都维护一个独立的并查集。每当两个节点合并时,将一个并查集合并到另一个并查集中,并修改合并后并查集中每个元素的信息。这样,当查询一个节点所在子树内元素的个数时,可以通过查询该节点所在并查集的元素个数来得到答案。

该算法的具体实现包括以下步骤:

  1. 从任意一个根节点开始,遍历整棵树,并对每个节点设立一个并查集。

  2. 对于每个节点,递归地处理其所有子节点,并将子节点所在的并查集合并到当前节点所在的并查集中。

  3. 对于每个查询,递归地处理树上的每个节点,并记录每个节点被查询到的次数。当一个节点被查询到时,将其所在的并查集中的所有元素插入到一个哈希表中,并记录元素出现的次数。当递归到叶子节点时,返回这个叶子节点所在哈希表中元素的个数。

时间复杂度

DSU on Trees | Set 2的时间复杂度为O(nlog_2(n)),其中n为树的节点数。

该算法的核心思想是将所有节点“分治”到其所属的子树中,并将每个子树所在的并查集合并到其父节点的并查集中。这样,查询一个节点所在子树内的元素个数时,可以通过查询该节点所在并查集的元素个数来得到答案。由于其中每个元素都对应一条路径,因此每个查询的时间复杂度为O(log_2(n))。

代码实现
vector<int> g[N];
int ans[N], sz[N];

void dfs(int u, int p) {
  sz[u] = 1;
  for (int v : g[u]) {
    if (v == p) continue;
    dfs(v, u);
    sz[u] += sz[v];
  }
  vector<int> ids;
  for (int v : g[u]) {
    if (v == p) continue;
    ids.push_back(v);
  }
  sort(ids.begin(), ids.end(), [&](int x, int y){
    return sz[x] < sz[y];
  });
  ans[u] = 0;
  for (int i = 0; i < ids.size(); i++) {
    int v = ids[i];
    if (i == 0) {
      ans[u] = ans[v];
    } else {
      for (auto [x, c] : mp[v]) {
        ans[u] += (c == 1);
        mp[u][x] += (c == 1);
      }
    }
    for (auto [x, c] : mp[v]) {
      mp[u][x] += c;
    }
  }
  mp[u][u] += 1;
}

以上代码使用了哈希表来记录每个节点所包含的元素。在dfs过程中,如果遍历到了某个节点,那么将该节点所在的所有元素都插入到哈希表中,并记录每个元素出现的次数。

在DSU on Trees | Set 2中,使用了一个比较巧妙的技巧,即按节点的子树大小排序。这是因为,子树大小越小的子树需要先被处理,这样才能保证后面统计答案时不会重复记数。