原题地址:http://acm.hdu.edu.cn/showproblem.php?pid=5923
题意:给你一个图
),
个点
条边。然后有一棵点数为
的树
,根为
, 树中每个点对应为图
的每条边
接下来有 次查询,每次查询有一个点数为 的集合 , 如果一个树中一个点在集合 中,那么它的祖先也都要加入集合 ,问由集合 组成的图 有多少个连通块
思路:可持久化并查集的应用,还不是特别懂
#include <bits/stdc++.h>
using namespace std;
#define maxn 10005
/*
树中的节点是图的边的信息
树中每一个节点都保存着一个并查集,表示的是当前树中的节点以及其祖先节点保存的信息
并查集中保存的图中节点之间关系,是否是联通的
fa[i][j]=k,表示的是在树的第i个节点保存的并查集中,图的节点j与节点k相连(节点j的父节点是k)
*/
int fa[maxn][505], cnt[maxn];//cnt[u]表示连了树上的u节点表示的信息之后,图中还有几个联通分量
int n, m, q;
vector <int> g[maxn];
int e[maxn][2];
int Find (int i, int x) {//合并
return fa[i][x] == x ? x : fa[i][x] = Find (i, fa[i][x]);
}
void dfs (int u, int father) {//初始化
if (father != u) {//当前u不是根节点
for (int i = 1; i <= n; i++) fa[u][i] = fa[father][i];//先复制
cnt[u] = cnt[father];
}
int p1 = Find (u, e[u][0]), p2 = Find (u, e[u][1]);//修改树上的信息
if (p1 != p2) {
fa[u][p1] = p2;
cnt[u]--;//联通块的数量
}
/*
1 2
2 3
3 1
这种情况
*/
int sz = g[u].size ();
for (int i = 0; i < sz; i++) {
int v = g[u][i];
dfs (v, u);
}
}
int main () {
int t, kase = 0;
scanf ("%d", &t);
while (t--) {
scanf ("%d%d", &n, &m);
for (int i = 1; i <= m; i++) g[i].clear ();
for (int i = 2; i <= m; i++) {
int father;
scanf ("%d", &father);
g[father].push_back (i);//树根连向树叶
}
for (int i = 1; i <= m; i++) {
scanf ("%d%d", &e[i][0], &e[i][1]);//树的节点信息,表示图中哪两个节点联通
}
cnt[1] = n;
for (int i = 1; i <= n; i++) fa[1][i] = i;//初始化并查集
dfs (1, 1);
scanf ("%d", &q);
printf ("Case #%d:\n", ++kase);
while (q--) {
int num, u;
scanf ("%d", &num);
scanf ("%d", &u);
for (int i = 1; i <= n; i++) fa[m + 1][i] = fa[u][i];//m+1这个是新的并查集
int tmp = cnt[u];
for (int i = 2; i <= num; i++) {
scanf ("%d", &u);
for (int j = 1; j <= n; j++) {//对比两个并查集的每一个位置
int x = Find (m + 1, j), y = Find (u, j);//Find第2维是节点,找到对应在并查集中根节点的编号
int p1 = Find (m + 1, x), p2 = Find (m + 1, y);//查询在的新的并查集中两者的根节点是否一样
if (p1 != p2) {//如果不一样就合并
tmp--;//联通分量数,如果连了一条边,那就说明来南通分量就会少一个
fa[m + 1][p1] = p2;//更新
}
}
}
printf ("%d\n", tmp);
}
}
return 0;
}