CodeForces 455C.Civilization(并查集)(树的直径)

题意:给出一个由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;
}
原文地址:https://www.cnblogs.com/pixel-Teee/p/13284332.html