📅  最后修改于: 2023-12-03 14:55:38.293000             🧑  作者: Mango
树上的扩展不相交集并集是一种常见的问题,其目标是在一棵树上维护若干不相交的集合,支持以下两种操作:
这个问题可以通过将树转换为一棵虚树,并在虚树上维护不相交集并集来解决。
虚树是一种建立在原树上的新树,其根节点为一个虚拟的节点,如果一个节点的祖先都在虚树中出现,则它也在虚树中出现,并且在虚树中包含了所有出现过的节点的路径。虚树的构建需要使用到深度优先搜索(DFS)和单调栈。
构建虚树的算法如下:
构建虚树的代码如下(假设节点的父节点已经被记录在数组 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;
}