title

「CH6201」走廊泼水节 - Kruskal

->戳我进原题

走廊泼水节


测试点数 10
总时限 10 s
总内存 256 MiB
通过率 41/55


描述

【简化版题意】给定一棵 \(N\) 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。求增加的边的权值总和最小是多少。

我们一共有 \(N\) 个OIER打算参加这个泼水节,同时很凑巧的是正好有 \(N\) 个水龙头(至于为什么,我不解释)。 \(N\) 个水龙头之间正好有 \(N-1\) 条小道,并且每个水龙头都可以经过小道到达其他水龙头(这是一棵树,你应该懂的..)。但是OIER门为了迎接中中的挑战,决定修建一些个道路(至于怎么修,秘密),使得每个水龙头到每个水龙头之间都有一条直接的道路连接(也就是构成一个完全图呗)。但是OIER门很懒得,并且记性也不好,他们只会去走那 \(N-1\) 条小道,并且希望所有水龙头之间修建的道路,都要大于两个水龙头之前连接的所有小道(小道当然要是最短的了)。所以神COW们,帮那些OIER们计算一下吧,修建的那些道路总长度最短是多少,毕竟修建道路是要破费的~~

输入格式

本题为多组数据~
第一行 \(t\) ,表示有 \(t\) 组测试数据
对于每组数据
第一行 \(N\) ,表示水龙头的个数(当然也是OIER的个数);
\(2\)\(N\) 行,每行三个整数 \(X,Y,Z\) ;表示水龙头 \(X\) 和水龙头 \(Y\) 有一条长度为 \(Z\) 的小道

输出格式

对于每组数据,输出一个整数,表示修建的所有道路总长度的最短值。

样例输入

2
3
1 2 2
1 3 3
4
1 2 3
2 3 4
3 4 5

样例输出

4
17

数据范围与约定

每个测试点最多10组测试数据
50% n<=1500;
100% n<=6000
100% z<=100

样例解释

第一组数据,在 \(2\)\(3\) 之间修建一条长度为4的道路,是这棵树变成一个完全图,且原来的树依然是这个图的唯一最小生成树.

思路

\(N\) 个节点的树有 \(N-1\) 条边。把这 \(N-1\) 条边按照权值从大到小排序,依次扫描每条边,执行一个类似于 \(Kruskal\) 算法的过程。

设当前扫描到边 \((x,y,z)\)\(x\) 所在的并查集为 \(S_x\)\(y\) 所在的并查集为 \(S_y\) ,此时应该合并 \(S_x\)\(S_y\) 。合并后的集合 \(S_x \cup S_y\) 构成一棵树的结构。

\(\forall u\in S_x,v\in S_y\) ,若 \((u,v)\neq(x,y)\) ,则在最终的完全图中, 我们肯定需要在 \((u,v)\) 之间增加一条边。于是无向边 \((u,v)\)\(S_x\) 中从 \(u\)\(x\) 的路径、无向边 \((x,y)\) 以及 \(S_y\) 中从 \(v\)\(y\) 的路径共同构成一个环。

此时,为了保证 \((x,y)\) 一定在最小生成树中,就必须让 \((x,y)\) 是连接集合 \(S_x\)\(S_y\) 的权值最小的边(否则就有“用 \((u,v)\) 替换 \((x,y)\) ”的方案)。因此,\((u,v)\) 的边权应该定为 \(z+1\)

\(S_x\)\(S_y\) 之间一共会增加 |\(S_x\)| \(*\) |\(S_y\)|\(-1\) 条边,所以我们把 \((z+1) * (\)|\(S_x\)| \(*\) |\(S_y\)|\(-1)\) 累加到答案中即可。算法时间复杂度为 \(O(N log N)\)

代码

#include<cstdio>
#include<cctype>
#include<iostream>
#include<cstring>
#include<algorithm>
#define rg register
#define int long long
using namespace std;
inline int read(){
	rg int f = 0, x = 0;
	rg char ch = getchar();
	while(!isdigit(ch))	f |= (ch == '-'), ch = getchar();
	while( isdigit(ch))	x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
	return f? -x: x;
}
const int N = 6010;
int T, n, f[N], ans, s[N];
struct edge{
	int u, v, w;
}e[N];
inline bool cmp(rg edge a, rg edge b){
	return a.w < b.w;
}
inline void init(){
	for(rg int i = 1; i <= n; ++i)	f[i] = i, s[i] = 1; //s表示集合初始化要包含本身所以是1 
	ans = 0; 
	memset(e,0,sizeof(e));
}
inline int find(int v){
	return f[v] == v? v: f[v] = find(f[v]);
}
inline void merge(rg int u, rg int v, rg int w){
	int x1 = find(u);
	int x2 = find(v);
	if(x1 != x2){
		ans += (w + 1) * (s[x1] * s[x2] - 1);
		f[x2] = x1;
		s[x1] += s[x2];
	}
}
signed main(){
	T = read();
	for(rg int o = 1; o <= T; ++o){
		n = read();
		init();
		for(rg int i = 1; i < n; ++i){
			e[i].u = read();
			e[i].v = read();
			e[i].w = read();
		}
		sort(e + 1, e + n, cmp);
		for(rg int i = 1; i < n; ++i)	merge(e[i].u, e[i].v, e[i].w);
		printf("%lld\n", ans);
	}
	return 0;
}
posted @ 2018-09-26 21:52  Horrigue_JyowYang  阅读(135)  评论(0编辑  收藏  举报