题意:给出一个由n个点,m条边组成的森林,两种类型共k次操作。
类型一:给出点x,输出点x所在的树的直径的大小。
类型二:给出点x,y(如果x,y在同一棵树中忽略此操作),在x所在树中选一个结点,在y所在的树中选择一个结点,连接这两个结点,要求使生成的树的直径最小。
分析:首先给出公式,假设x所在的树的直径为del1,y所在的树的直径为del2,那么合并后的树的直径为(max(max(del1, del2), frac{(del1 + 1)}{2} + frac{(del2 + 1)}{2} + 1))。我们可以用并查集维护合并后的树,假设这棵合并后的树的代表元素为p,那么它的直径就为新的值。求取树的直径可以用dfs。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 300005;
int h[N], e[N * 2], ne[N * 2], idx;
bool st[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int p[N];
//每个集合的最长直径
int deli[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int res = 0;
int now;
int dfs(int u, int fa)
{
st[u] = true;
//合并到根节点
p[u] = now;
//以这个点为枢纽的最长路径
int dist = 0;
int d1 = 0, d2 = 0;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (j == fa) continue;
int d = dfs(j, u) + 1;
dist = max(dist, d);
if (d >= d1) d2 = d1, d1 = d;
else if (d > d2) d2 = d;
}
res = max(res, d1 + d2);
return dist;
}
int main()
{
//点、边、操作次数
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i) p[i] = i;
memset(h, -1, sizeof h);
int u, v;
for (int i = 1; i <= m; ++i)
{
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
for (int i = 1; i <= n; ++i)
{
if (!st[i])
{
st[i] = true;
now = i;
dfs(i, -1);
deli[i] = res;
res = 0;
}
}
int op, x, y;
for (int i = 1; i <= k; ++i)
{
scanf("%d", &op);
if (op == 1)
{
scanf("%d", &x);
int pa = find(x);
printf("%d
", deli[pa]);
}
else
{
scanf("%d%d", &x, &y);
int pa = find(x), pb = find(y);
if (pa != pb)
{
int de1 = deli[pa], de2 = deli[pb];
int ndel = max(max(de1, de2), (de1 + 1) / 2 + (de2 + 1) / 2 + 1);
p[pa] = pb;
deli[pb] = ndel;
}
}
}
return 0;
}