构造完全图

走廊泼水节(构造完全图)ACWing346

题意:

给你一棵树,将这棵树扩充为完全图,满足该图的最小生成树为这棵树。求增加边的权值最小是多少。

思路:

根据完全图的性质,每两个点都至少有一条边联通。再想 \(Kruskal\) 算法的实现流程,我们只需要在合并两个单独并查集的时候,记录一下贡献就可以了。因为在 \(Kruskal\) 合并两个并查集的时候只是保留了一条边权最小的边使两个单独的联通块 \(Z\)\(Y\) 联通。所以如要构成完全图,根据乘法原理,每次操作的贡献就为 $\large S_Z * S_Y - 1 $ ,$ \large S $ 为联通块中节点的个数,减去的是已经被加入 \(MST\) 中的树边。由于要满足扩充后的完全图仍然为这棵树, 那么加入的非树边的权值需要严格大于树边(如果小于就会被加入 \(MST\) 中使原树结构被改变),同时又要满足 增加边的权值最小 所以这里每次合并的贡献就为 \(\large (S_Z * S_Y - 1 ) * (w + 1)\) \(w\) 为树边的权值。

code

#include <bits/stdc++.h>
const int maxn = 2e5 + 7 ;
using namespace std ;
struct node 
{
    int frm , to , nxt , dis ;
} ed[maxn] ;
bool operator < (node x , node y)
{
	return x.dis < y.dis ;
}
int n , m , cnt , tot , ans , t  ;
int head[maxn] , fa[maxn] , s[maxn] ;
int find(int x)
{
	if(x == fa[x])	return x ;
	return fa[x] = find(fa[x]) ;
} 
void add(int u , int v , int w)
{
    ed[++cnt] = { u , v , head[u] , w } ;
    head[u] = cnt ;
}
signed main()
{
    cin >> t ;
    while( t-- )
    {
        memset(head , 0 , sizeof(head)) ;
        memset(ed , 0 , sizeof(ed)) ;
        cin >> n ;  cnt = 0 , tot = 0 , ans = 0 ;   
 		for(int i = 0 ; i <= 2*n ; i++ )	fa[i] = i , s[i] = 1 ; 
        for(int i = 1 ; i < n ; i++ )
        {
            int u , v , w ;     cin >> u >> v >> w ; 
            add(u , v , w) ;    add(v , u , w) ;
        }
        sort(ed + 1 , ed + 2*n + 1) ; 
        for(int i = 1 ; i <= 2*n ; i++ )
        {
            int u = find(ed[i].frm) , v = find(ed[i].to) ;
            if(u == v)  continue ;
            ans += (ed[i].dis + 1) * (s[u] * s[v] - 1) ;
            fa[v] = u ; s[u] += s[v] ;  tot++ ;
        //cout << "suz_ak_ioi" << s[v]  ;
            if(tot == n)    break ;
        }
        cout << ans << '\n' ;
    }
    return 0 ; 
}
posted @ 2022-04-05 17:44  Simon_...sun  阅读(209)  评论(0)    收藏  举报