前言
这道题A了之后莫名其妙就想打个题解
题目
给定一棵 N 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。
求增加的边的权值总和最小是多少。
注意: 树中的所有边权均为整数,且新加的所有边权也必须为整数。
分析
这道题最后要求最终生成的完全图的最小生成树是给定的原树,我们很容易想到类似Kruskal的想法。首先把边按照从小到大进行排序,每次对于给定的一条边而言,因为这条边要成为最小生成树上的边,所以这条边所连接的两个连通块内的其他边都要比这条边大,要让增加的权值总和最小我们只要让其他边的权值都比这条边的权值大1就OK啦。
为什么要从小到大排序呢?我们可以考虑Kruskal的过程,如果这条边是当前剩下的连通两个连通块的所有边中的最小边,那么它一定在最小生成树中。我们考虑本题的过程,如果边是从小到大枚举的话,那么显然剩下还没连接的部分的连通块里面的边一定比当前这个边要大。所以从小到大枚举可以保证结果的正确性。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int father[6010];
int sze[6010];
ll ans;
struct st
{
int x,y,val;
} side[6010];
int getfather(int x)
{
if(father[x]==x) return x;
return father[x]=getfather(father[x]);
}
void merge(int x,int y,int val)
{
int fx=getfather(x);
int fy=getfather(y);
if(fx!=fy)
{
ans+=1LL*(1LL*sze[fx]*sze[fy]-1)*(val+1);
sze[fy]+=sze[fx];
sze[fx]=0;
father[fx]=fy;
}
}
bool cmp(st r1,st r2)
{
return r1.val<r2.val;
}
void init()
{
ans=0;
scanf("%d",&n);
for(int i=1;i<n;i++)
scanf("%d%d%d",&side[i].x,&side[i].y,&side[i].val);
sort(side+1,side+n,cmp);
for(int i=1;i<=n;i++)
{
sze[i]=1;
father[i]=i;
}
}
void work()
{
for(int i=1;i<n;i++)
{
merge(side[i].x,side[i].y,side[i].val);
}
}
void print()
{
printf("%lld\n",ans);
}
int main()
{
int T;
cin>>T;
while(T--)
{
init();
work();
print();
}
return 0;
}