赞
踩
题目的意思是:现在有一张图,已知了这张图的最小生成树了,然后我们需要把这张图变成完全图(任意两个节点之间都有边相连),使得这张完全图中的最小生成树仍然与原图的最小生成树是同一颗。把原图变成完全图,就需要加边,题目想要我们求的是加的这些边的权值总和的最小值。
设当前扫描到边 ( x , y ) (x,y) (x,y),其权值为 z z z,设 x x x所在连通块为 S x S_x Sx, y y y所在连通块为 S y S_y Sy,此时应该合并 S x S_x Sx与 S y S_y Sy。合并后的 S x ∪ S y S_x\cup S_y Sx∪Sy构成一棵树的结构。
∀ u ∈ S x \forall u\in S_x ∀u∈Sx, v ∈ S y v\in S_y v∈Sy,如果 ( u , v ) ≠ ( x , y ) (u,v)\neq(x,y) (u,v)=(x,y),则在最终的完全图中,我们肯定需要在 ( u , v ) (u,v) (u,v)之间增加一条边。于是,无向边 ( u , v ) (u,v) (u,v)、 S x S_x Sx中从 u u u到 x x x的路径、无向边 ( x , y ) (x,y) (x,y)、 S y S_y Sy中从 v v v到 y y y的路径,就形成了一个环,如下图所示:
为了保证 ( x , y ) (x,y) (x,y)一定在最小生成树中,就必须让 ( x , y ) (x,y) (x,y)是连接集合 S x S_x Sx和集合 S y S_y Sy的权值最小的边(否则就可以用边 ( u , v ) (u,v) (u,v)去代替边 ( x , y ) (x,y) (x,y)形成一棵最小生成树了,这与已有的最小生成树有矛盾)。因此,为了保证所添加的这些边的权值总和最小,那么可以让所有新添加的这些边的权值都为 z + 1 z+1 z+1。
设集合 S x S_x Sx中有 ∣ S x ∣ |S_x| ∣Sx∣个节点,集合 S y S_y Sy中有 ∣ S y ∣ |S_y| ∣Sy∣个节点,由于完全图是任意两个节点之间都需要有边相连,因此总共需要 ∣ S x ∣ × ∣ S y ∣ |S_x|\times|S_y| ∣Sx∣×∣Sy∣条边,但是由于边 ( x , y ) (x,y) (x,y)已经是原图中已有的一条连接 S x S_x Sx和 S y S_y Sy这两个连通块中的一条边了,因此应该从 ∣ S x ∣ × ∣ S y ∣ |S_x|\times|S_y| ∣Sx∣×∣Sy∣减去这条已有的连接边。也就是说,合并 S x S_x Sx和 S y S_y Sy这两个连通块,需要边数为 ∣ S x ∣ × ∣ S y ∣ − 1 |S_x|\times|S_y|-1 ∣Sx∣×∣Sy∣−1,由于每条边的权值都为 z + 1 z+1 z+1。因此增加的边的权值总和最小是 ( z + 1 ) × (z+1)\times (z+1)× ( ∣ S x ∣ × ∣ S y ∣ − 1 ) (|S_x|\times|S_y|-1) (∣Sx∣×∣Sy∣−1)
问题:为什么新添加的边权一定是 z + 1 z+1 z+1,而不能是 z z z或者小于 z z z呢?如何理解 ( z + 1 ) × (z+1)\times (z+1)× ( ∣ S x ∣ × ∣ S y ∣ − 1 ) (|S_x|\times|S_y|-1) (∣Sx∣×∣Sy∣−1)这个式子呢?
如下图所示:可以发现,合并了两个连通块后,所形成的这个连通块就已经是完全图了,以此类推,合并完所有的连通块后,所得到的最终那个连通块就是完全图。
总结一下就是:
假设有两个连通块 A , B A,B A,B,要扩展为完全图,那么就需要 A A A中的每个节点都与 B B B中的每个节点相连。对于一条最小生成树上的边 E E E,可以看作 E E E连接了 A A A和 B B B这两个连通块,那么要将 A A A和 B B B连接成一个完全图需要添加的边数就是 c n t [ A ] × c n t [ B ] − 1 cnt[A]\times cnt[B]-1 cnt[A]×cnt[B]−1,减去1就是减去已经存在的 E E E这条边,其中 c n t [ A ] cnt[A] cnt[A]表示连通块 A A A中的节点数, c n t [ B ] cnt[B] cnt[B]表示连通块 B B B中的节点数。设 E E E的边权为 z z z,由上图的可知,新增加的边权不可能 ≤ z \leq z ≤z,必须是 > z >z >z的。为了让增加的边权总和最小,则需要让新增加的边权为 z + 1 z+1 z+1就好了。
那么合并这两个连通块的花费就是
(
z
+
1
)
×
(
c
n
t
[
A
]
×
c
n
t
[
B
]
−
1
)
(z+1)\times(cnt[A]\times cnt[B]-1)
(z+1)×(cnt[A]×cnt[B]−1)。设ans
是答案,那么在每次合并连通块时,让
a
n
s
ans
ans累加上花费就好了。
还有一点,题目要求的是最小的完全图,因此采用贪心的策略:先把树上的边按权值从小到大排序,然后依次枚举即可。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=6010,M=N;
//n是点数 m是边数
int n,m;
struct Edge{
int a,b,w;
bool operator < (const Edge& W)const{
return w<W.w;
}
}edges[M];
int p[N];
int cnt[N]; //cnt[i]表示第i个连通块中点的数量
int find(int x)
{
if(x!=p[x])
p[x]=find(p[x]);
return p[x];
}
int Kruskal()
{
int res=0; //增加的边的权值总和最小值
sort(edges,edges+m); //将边权从小到大排序
for(int i=1;i<=n;i++)
{
p[i]=i; //初始化并查集 每个点都是独立的连通块
cnt[i]=1; //由于每个点都是独立的连通块 所以每个连通块内只有1个节点
}
for(int i=0;i<m;i++)
{
int a=find(edges[i].a);
int b=find(edges[i].b);
int w=edges[i].w;
if(a!=b)
{
//每次合并两个连通块都会产生(cnt[a]*cnt[b]-1)*(w+1)的开销
//所以要让res累加这些开销
res+=(cnt[a]*cnt[b]-1)*(w+1);
//将a这个连通块中所有点的数量都累加到b这个连通块中
cnt[b]+=cnt[a];
//将集合a合并到集合b,现在a的祖宗节点是b
p[a]=b;
}
}
return res;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
//根据题目输入可以知道 有n个点 则有n-1条边
m=n-1;
//读入m条边的信息
for(int i=0;i<m;i++)
{
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
edges[i]={a,b,w};
}
int t=Kruskal();
printf("%d\n",t);
}
return 0;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。