CH 6201 走廊泼水节(进阶指南,kruskal最小生成树)

算法竞赛进阶指南, 366页, kruskal 算法
本题要点:
1、n个点的树, 有 n - 1条边。要补充 n * (n - 1) / 2 - (n - 1) 条边,然后变成
一个完全图(一共 n * (n - 1) / 2 条边)。要求原来的树,是完全图唯一的最小生成树。
显然,补充的每一条边(x, y),都比从 x 点 和y 点出发的边都要长;
2、根据 kruskal 算法进行计算:
当处理到边 (x, y, z) 的时候,x点 所在的集合 为 s[x], y点所在的集合为 s[y]。
显然,两个集合互相连边,一共有 s[x] * s[y] 条,边(x, y, z) 肯定是最短的,否则这棵树就不是唯一的
最小生成树; 两个集合合并,需要加 s[x] * s[y] - 1 条边(集合 s[x] 的内部已经全部两两相连了),
这些边的最小距离是 z + 1, 因此答案累加上 ans += (s[x] * s[y] - 1) * (z + 1)

#include 
#include 
#include 
#include 
using namespace std;
const int MaxN = 6010;

struct rec
{
	int x, y, z;
	bool operator<(const rec& rhs) const
	{
		return z < rhs.z;
	}
}edges[MaxN];

int fa[MaxN];
int s[MaxN];		//s[i] 表示以i点为父节点的点有多少个
int n, T;

int get(int x)
{
	if(x == fa[x])
	{
		return x;
	}
	return fa[x] = get(fa[x]);
}

void solve()
{
	long long ans = 0;
	for(int i = 1; i <= n; ++i)
	{
		fa[i] = i;
		s[i] = 1;
	}
	sort(edges + 1, edges + n);
	for(int i = 1; i < n; ++i)
	{
		int x = get(edges[i].x);	
		int y = get(edges[i].y);
		if(x == y)
		{
			continue;
		}
		ans += (long long)(edges[i].z + 1) * (s[x] * s[y] - 1);
		fa[x] = y;
		s[y] += s[x];
	}
	printf("%lld\n", ans);
}

int main()
{
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d", &n);
		for(int i = 1; i < n; ++i)
		{
			scanf("%d%d%d", &edges[i].x, &edges[i].y, &edges[i].z);
		}
		solve();
	}
	return 0;
}

/*
2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5 
*/

/*
4
17
*/

你可能感兴趣的:(POJ,算法竞赛进阶指南,图论)