这是一道并查集的简单维护题
题目链接(luogu)
CodeForces
题意
给出一棵森林,有两种操作
1
。
1 ^。
1。 给出点
x
x
x,输出点
x
x
x 所在的树的直径
2
。
2 ^。
2。 给出点
x
,
y
x,y
x,y,(如果
x
,
y
x,y
x,y 在同一棵树中则忽略此操作)选择任意两点
u
∈
x
u \in x
u∈x所在子树,
v
∈
y
v \in y
v∈y所在子树 ,将
u
,
v
u,v
u,v 之间连一条边,使得连边后的到的新树的直径最小。
思路
首先考虑查询操作:
我们用两遍
d
f
s
dfs
dfs 求出每一颗子树树的直径,并顺便更新每一个点的从属情况
接下来考虑合并操作:
因为是使新树的直径最小
而这个直径的更新从三个方面来
d
n
e
w
=
m
i
n
(
d
x
,
d
y
,
⌈
d
x
2
⌉
+
⌈
d
y
2
⌉
2
+
1
)
d_{new} = min(d_x, d_y, \frac{\lceil \frac{d_x}{2} \rceil + \lceil \frac{d_y}{2} \rceil}{2} + 1)
dnew=min(dx,dy,2⌈2dx⌉+⌈2dy⌉+1)
故可以在合并的时候将新树的
d
d
d维护出来
代码
具体实现见代码:
const int N = 3e5 + 5;
int n, m, q, mxindex, mxval, fa[N], d[N], belong;
vector<int> G[N];
void dfs(int x, int pre, int sum)
{
fa[x] = belong;
if (sum > mxval)
{
mxindex = x;
mxval = sum;
}
for (auto it : G[x])
if (it != pre)
dfs(it, x, sum + 1);
}
int findset(int x)
{
return fa[x] == x ? fa[x] : fa[x] = findset(fa[x]);
}
void merge(int x, int y)
{
x = findset(x), y = findset(y);
if (x != y)
{
if (d[x] < d[y])
swap(x, y);
d[x] = max(d[x], (d[x] + 1) / 2 + (d[y] + 1) / 2 + 1);
fa[y] = x;
}
}
int main()
{
rd(n), rd(m), rd(q);
for (int i = 1; i <= n; i++)
fa[i] = i;
for (int i = 1, x, y; i <= m; i++)
rd(x), rd(y), G[x].push_back(y), G[y].push_back(x);
for (int i = 1; i <= n; i++)
if (fa[i] == i)
{
belong = i;
mxindex = mxval = 0;
dfs(i, 0, 0);
belong = mxindex;
dfs(mxindex, 0, 0);
d[belong] = mxval;
}
while (q--)
{
int opt, x, y;
rd(opt), rd(x);
if (opt == 1)
printf ("%d\n", d[findset(x)]);
else
rd(y), merge(x, y);
}
return 0;
}