BZOJ1977 [BeiJing2010组队] [次小生成树 Tree]

本文介绍了一种求解无向图中次小生成树的问题,并给出了一套完整的算法实现方案。通过先构建最小生成树,再利用树上倍增技巧来替换边,最终找出次小生成树的边权总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Description

小 C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 e的权值)  这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

Input

第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。

Output

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

Sample Input

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

Sample Output

11

HINT

数据中无向图无自环; 50% 的数据N≤2 000 M≤3 000; 80% 的数据N≤50 000 M≤100 000; 100% 的数据N≤100 000 M≤300 000 ,边权值非负且不超过 10^9 。

解题报告:树上倍增+最小生成树

这道题知识点简单,但是考察代码能力和细节处理。我采用的是求LCA的方法(常数大)思路就是先求出最小生成树,然后用不在树上的边来代替树上的边,然后问题就转化为了求出树上两点间最大边权与次大边权,在处理合并的时候有一些细节要处理(具体看代码)

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7 ;
const int P = 18 ;
struct Edge{
    int u , v , w ;
    Edge ( int u=0 , int v=0 , int w=0 ) : u(u) , v(v) , w(w) {}
};
Edge e [N<<2];
bool cmp ( Edge x , Edge y ) {
    return x.w < y.w ;
}
 
vector <Edge> edges;
vector <int> G [N] ;
 
inline int readin(){
	int x=0 , f=1 ; char ch = getchar ();
	while ( !isdigit ( ch ) ){
		if ( ch == '-' ) f = -1 ;
		ch = getchar ();
	}
	while ( isdigit ( ch ) ){
		x = x * 10 + ch - '0' ;
		ch = getchar();
	}
	return x * f ;
}

void addeage ( int u , int v , int w ) {
    edges.push_back ( Edge ( u , v , w ) ) ;
    int tot = edges.size () ;
    G [u].push_back ( tot - 1 ) ;
}
 
int fa [N] [P] , fm [N] [P] , sm [N] [P] , dep [N] , vis [N<<2] , ans;
long long ret = 0;
 
int dfs ( int u ){
    for ( int i = 1 ; i < P ; ++ i ) {
        fa [u] [i] = fa [ fa [u] [i-1] ] [i-1] ;
        int t1 = fm [u] [i-1] , t2 = fm [ fa [u] [i-1] ] [i-1] ;
        fm [u] [i] = max ( t1 , t2 ) ;
        sm [u] [i] = max ( sm [u] [i-1] , sm [ fa [u] [i-1] ] [i-1] ) ;
        if ( t1 != t2 ) sm [u] [i] = max ( sm [u] [i] , min ( t1 , t2 ) );      
    }
    for ( int i = 0 ; i < G [u].size () ; ++ i ) {
        Edge e = edges [ G [u] [i] ] ;
        if ( e.v == fa [u][0] ) continue ;
        dep [e.v] = dep [u] + 1 ;
        fa [e.v][0] = u ;
        fm [e.v][0] = e.w ; 
        dfs ( e.v );
    }
}
 
int LCA ( int u , int v ) {
    if ( dep [u] < dep [v] ) swap ( u , v ) ;
    int h = dep [u] - dep [v] ;
//  printf ( "h: %d \n" , h );
    for ( int i = P ; i >= 0 && h ; -- i ) if ( h & (1<<i) ){
        u = fa [u][i] ;
//      printf ( " u = %d\n" , u ) ;
    }
    if ( u == v ) return u ;
    for ( int i = P-1 ; i >= 0 ; -- i ) {
        if ( fa [u] [i] != fa [v] [i] ) {
            u = fa [u] [i] ;
            v = fa [v] [i] ;
//          printf ( "%d %d %d\n" , i , u , v ) ;
        }
    }
    return fa [u] [0] ;
}
 
void query ( int u , int x , int v ) {
    int t = dep [u] - dep [x];
    int fmx , smx ;
    fmx = smx = 0 ;
//  printf ( "----u = %d  x = %d----\n" , u , x ) ;
    for ( int i = P ; i >= 0 ; -- i ) if ( t & ( 1<<i ) ) {
        if ( fm [u] [i] > fmx ){
            smx = fmx ;
            fmx = fm [u] [i] ;
        }else if ( fm [u] [i] != fmx ) smx = max ( smx , fm [u] [i] ); 
        //
        smx = max ( smx , sm [u] [i] ) ;
        u = fa [u] [i] ;  
    }
//  printf ( "fmx = %d , smx = %d\n" , fmx , smx ) ;
    if ( v == fmx ) ans = min ( ans , v - smx ) ;
    else if ( fmx < v ) ans = min ( ans , v - fmx ) ;
}

void work ( int i ){
    int u = e [i].u , v = e [i].v , w = e [i].w ;
    int x = LCA ( u , v ) ;
    query ( u , x , w ) ;
    query ( v , x , w ) ;
}
 
int fat [N] ;
 
int find ( int u ) {
    return u == fat [u] ? u : fat [u] = find ( fat [u] ) ;
}
 
void Kruskal ( int n , int m ) {
    int tot = 0 ;
    for ( int i = 1 ; i <= n ; ++ i ) fat [i] = i ;
    sort ( e + 1 , e + 1 + m , cmp ) ;
    for ( int i = 1 ; i <= m ; ++ i ) {
        int u = e [i].u , v = e [i].v , w = e [i].w ;
        int fu = find ( u ) ;
        int fv = find ( v ) ;
        if ( fu != fv ) {
            fat [fu] = fv ;
            tot ++ ;
            ret += w ;
            vis [i]  = 1;
            addeage ( u , v , w ) ;
            addeage ( v , u , w ) ;
    //      printf ( "%d %d %d\n" , u , v , w ) ;
            if ( tot == n - 1 ) break;
        } 
    }
}
 
int main () {
    int n , m ;
//    scanf ( "%d%d", &n ,&m );
	n = readin() , m = readin();
	for ( int i = 1 ; i <= m ; ++ i ) {
		e [i].u = readin() , e [i].v = readin() , e [i].w = readin();
//        scanf ( "%d%d%d" , & e [i].u , & e [i].v , & e [i].w ) ;
    }
    ret = 0 , ans = 1e9 + 10 ;
    Kruskal ( n , m ) ;
    dep [1] = 1;
    dfs ( 1 ) ;
    int tota = 0;
    for ( int i = 1 ; i <= m ; ++ i ) if ( ! vis [i] ){
        work ( i ) ;
        tota ++;
    }
    if ( !tota ) ans = 0 ;
    printf ( "%lld" , (long long)ret + ans ) ;
 
    return 0 ;
}

























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值