📜  树上的扩展不相交集并集(1)

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

树上的扩展不相交集并集

简介

树上的扩展不相交集并集是一种常见的问题,其目标是在一棵树上维护若干不相交的集合,支持以下两种操作:

  1. 将某个节点加入一个集合中;
  2. 求出某个节点所在集合的并集。

这个问题可以通过将树转换为一棵虚树,并在虚树上维护不相交集并集来解决。

算法思路
虚树

虚树是一种建立在原树上的新树,其根节点为一个虚拟的节点,如果一个节点的祖先都在虚树中出现,则它也在虚树中出现,并且在虚树中包含了所有出现过的节点的路径。虚树的构建需要使用到深度优先搜索(DFS)和单调栈。

构建虚树的算法如下:

  1. 将原树按照 DFS 序排序,并建立单调递减的节点栈 st;
  2. 从节点栈中取出第一个点 u,其在原树上的父节点为 v;
  3. 如果 v 不在节点栈中,则将 u 加入虚树中,u 的父节点为 v;否则在节点栈中找到 v,将 u 加入虚树中,v 的后继就是 u;
  4. 重复步骤 2 和步骤 3 直到节点栈中只剩下一个元素,即根节点。

构建虚树的代码如下(假设节点的父节点已经被记录在数组 p 中):

vector<int> adj[MAXN];
int dfn[MAXN], low[MAXN], p[MAXN], ti;
stack<int> st;

void dfs(int u) {
    dfn[u] = low[u] = ++ti;
    st.push(u);
    for (int v : adj[u]) {
        if (!dfn[v]) {
            dfs(v);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                int w;
                do {
                    w = st.top(); st.pop();
                    p[w] = u;
                } while (w != v);
                adj[u].push_back(v);
                adj[v].push_back(u);
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

int virt[MAXN], vis[MAXN], sz[MAXN], ts;
void build_virtual_tree(int rt, int n) {
    ts = 1;
    for (int i = 1; i <= n; ++i) {
        virt[i] = vis[i] = p[i] = 0;
        sz[i] = 1;
        adj[i].clear();
    }
    while (!st.empty()) st.pop();
    dfs(rt);
    while (st.size() > 1) {
        int u = st.top(); st.pop();
        virt[u] = ++ts;
        adj[u].push_back(st.top());
        adj[st.top()].push_back(u);
    }
    virt[st.top()] = 1;
}
不相交集合

树上不相交集合的维护通常使用基于并查集的算法。我们需要为每个节点维护一个独立的并查集,初始情况下每个节点都是一个单独的集合。在加入某个节点之前,我们需要先将其加入到虚树的路径上,然后合并路径上所有节点所在的集合。求出某个节点所在集合的并集时,我们可以在虚树上向上查找其所有祖先所在集合的根节点,即可求出当前节点所在集合的并集。

不相交集合的实现使用路径压缩和按秩合并可以达到较好的效率,在本文中不再赘述。具体实现可以参考代码片段。

代码实现
虚树的构建
vector<int> adj[MAXN];
int dfn[MAXN], low[MAXN], p[MAXN], ti;
stack<int> st;

void dfs(int u) {
    dfn[u] = low[u] = ++ti;
    st.push(u);
    for (int v : adj[u]) {
        if (!dfn[v]) {
            dfs(v);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                int w;
                do {
                    w = st.top(); st.pop();
                    p[w] = u;
                } while (w != v);
                adj[u].push_back(v);
                adj[v].push_back(u);
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

int virt[MAXN], vis[MAXN], sz[MAXN], ts;
void build_virtual_tree(int rt, int n) {
    ts = 1;
    for (int i = 1; i <= n; ++i) {
        virt[i] = vis[i] = p[i] = 0;
        sz[i] = 1;
        adj[i].clear();
    }
    while (!st.empty()) st.pop();
    dfs(rt);
    while (st.size() > 1) {
        int u = st.top(); st.pop();
        virt[u] = ++ts;
        adj[u].push_back(st.top());
        adj[st.top()].push_back(u);
    }
    virt[st.top()] = 1;
}
不相交集合的实现
int fa[MAXN], siz[MAXN];

inline int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

inline void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x == y) return;
    if (siz[x] < siz[y]) swap(x, y);
    fa[y] = x;
    siz[x] += siz[y];
}
完整代码

下面是树上扩展不相交集并集的完整代码,包括虚树的构建和不相交集合的实现。

#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#include <algorithm>

using namespace std;
const int MAXN = 1e5 + 10;

vector<int> adj[MAXN];
int dfn[MAXN], low[MAXN], p[MAXN], ti;
stack<int> st;

void dfs(int u) {
    dfn[u] = low[u] = ++ti;
    st.push(u);
    for (int v : adj[u]) {
        if (!dfn[v]) {
            dfs(v);
            low[u] = min(low[u], low[v]);
            if (low[v] >= dfn[u]) {
                int w;
                do {
                    w = st.top(); st.pop();
                    p[w] = u;
                } while (w != v);
                adj[u].push_back(v);
                adj[v].push_back(u);
            }
        } else {
            low[u] = min(low[u], dfn[v]);
        }
    }
}

int virt[MAXN], vis[MAXN], sz[MAXN], ts;
void build_virtual_tree(int rt, int n) {
    ts = 1;
    for (int i = 1; i <= n; ++i) {
        virt[i] = vis[i] = p[i] = 0;
        sz[i] = 1;
        adj[i].clear();
    }
    while (!st.empty()) st.pop();
    dfs(rt);
    while (st.size() > 1) {
        int u = st.top(); st.pop();
        virt[u] = ++ts;
        adj[u].push_back(st.top());
        adj[st.top()].push_back(u);
    }
    virt[st.top()] = 1;
}

int fa[MAXN], siz[MAXN];

inline int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

inline void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x == y) return;
    if (siz[x] < siz[y]) swap(x, y);
    fa[y] = x;
    siz[x] += siz[y];
}

inline int query(int u) {
    int res = find(u);
    while (virt[res] > 1) {
        res = find(p[res]);
    }
    return res;
}

int main() {
    int n, m, rt;
    scanf("%d%d%d", &n, &m, &rt);
    for (int i = 1; i <= n; ++i) fa[i] = i;
    for (int i = 1; i <= m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        merge(u, v);
    }
    build_virtual_tree(rt, n);
    for (int i = 1; i <= n; ++i) {
        int u = i;
        while (virt[u] > 1) {
            merge(u, p[u]);
            u = p[u];
        }
    }
    for (int i = 1; i <= n; ++i) {
        printf("%d ", query(i));
    }
    printf("\n");
    return 0;
}
参考文献
  1. 张昆玮. 虚树讲解及其应用[C]. 2015. (PDF
  2. OldDog. 【模板】树上启发式合并【数据结构】[ZJOI2016]全球眼看上海. 2017. (Link