Description
对于完全图G,若有且仅有一棵最小生成树为T,则称完全图G是树T的扩展出的。给你一棵树T,找出T能扩展出的边权和最小的完全图G。
Input
第一行N,(2 <= N <= 10^5)表示树T的点数。
接下来N-1行,Si Ti Di 描述一条边(Si,Ti)权值为 Di。
保证输入数据构成一棵树。
Output
一行一个数,表示最小的图G的边权和。
Sample Input
4
1 2 1
1 3 1
1 4 2
Sample Output
12
Hint
N≤100000,1≤Di≤100000
【分析】
这道题目跟Kruskal有关,因为Kruskal的方法是每次将两块独立的图由一条最小的边连接起来。
所以,连通这两块图的 其它的边的数量=(cnt[fx]*cnt[fy]-1)
即两个集合的点数乘积,减去正在讨论的这条边。
而 其他边的值=edge[i].z(最小边的值)+1,那么我们将题目中给出的边排序,然后从小到大每次加入一条边,用并查集找到它们的所属集合,
再将 ans+=(cnt[fx]*cnt[fy]-1)*(edge[i].z+1);(fx,fy表示边的两端点所属集合)
然后 father[fx]=fy;cnt[fy]+=cnt[fx];(合并两个集合)
(注:存边和答案要用long long,不然会WA)
【代码】
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
using namespace std;
struct wjj{
long long x,y,z;
}edge[100005]; //边 注意用long long
int N,father[100005]; //并查集记录所属集合
long long cnt[100005]; //记录每个集合的点的数量
long long ans;
int _getfather(int x) //带路径压缩的并查集
{
if(x!=father[x]) father[x]=_getfather(father[x]);
return father[x];
}
void _in(long long &x) //输入优化
{
char t=getchar();
while(t<'0'||'9'<t) t=getchar();
for(x=t-'0',t=getchar();'0'<=t&&t<='9';x=x*10+t-'0',t=getchar());
}
void _init()
{
scanf("%d",&N);
for(int i=1;i<N;i++)
{
_in(edge[i].x);
_in(edge[i].y);
_in(edge[i].z);
ans+=edge[i].z; //注意输出的是完全图的权值和,要算树T中的
}
for(int i=1;i<=N;i++)
{
father[i]=i; //初始化并查集
cnt[i]=1;
}
}
void _qst_z(int l,int r)
{
int i=l,j=r,mz=edge[(i+j)>>1].z;
while(i<=j)
{
while(edge[i].z<mz) i++;
while(edge[j].z>mz) j--;
if(i<=j)
{
swap(edge[i],edge[j]);
i++;j--;
}
}
if(l<j) _qst_z(l,j);
if(i<r) _qst_z(i,r);
}
void _solve()
{
int fx,fy;
_qst_z(1,N-1); //排序
for(int i=1;i<N;i++)
{
fx=_getfather(edge[i].x);
fy=_getfather(edge[i].y);
ans+=(cnt[fx]*cnt[fy]-1)*(edge[i].z+1); //计算答案
father[fx]=fy; //合并集合
cnt[fy]+=cnt[fx];
}
printf("%I64d\n",ans);
}
int main()
{
_init();
_solve();
return 0;
}