[BZOJ1977]严格次小生成树-kruskal+倍增维护

写在前面

一定要三思而后submit啊hhhhh


题目

这貌似是一道权限题…但是还是放一个传送门吧qwq
BZOJ1977传送门

题面

小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:包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

样例

(其实这个样例还可以)

simple IN:
5 6
1 2 1
1 3 2
2 4 3
3 5 4
3 4 3
4 5 6
simple OUT:
11


解法

本题就是要求一个严格的次小生成树,严格就是说,这个次小生成树的边权和大于(不能取等)最小生成树

一般的次小生成树就是,枚举每条边e( u , v ),找u->v路径中权值最大的那条边,用替换之后的边权和更新答案。

树上两点路径维护,倍增肯定是少不了的,关键是要维护什么值。
因为这个次小是严格次小,因此如果length( e ) 和 length( p )相等明显不符合题意。为了解决这种情况,我们需要找一条次大的边p1,并用e去替换p1,这样才能保证是严格次小。
因此,倍增维护需要维护三个值:fa , 最大值 , 次大值。

注意这个次大值是不能和最大值相等的,维护的时候有一点小技巧,想清楚了再写不容易错。细节看代码。


下面是自带大常数的代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N , M , tp , head[100005] ;
long long sum , ans ;
struct Edge{
    int u , v , val ;
    bool ontree ;
    bool operator < ( const Edge &A ) const {
        return val < A.val ;
    }
}e[300005] ;
struct Path{
    int pre , to , val ;
}p[100005<<2] ;

class Union_set{
    public :
        void init(){
            for( int i = 0 ; i <= N ; i ++ )
                fa[i] = i ;
        }
        int find( int x ){
            if( fa[x] == x ) return x ;
            return fa[x] = find( fa[x] ) ;
        }
        bool Union( int x , int y ){
            int F_x = find( x ) ;
            int F_y = find( y ) ;
            if( F_x == F_y ) return false ;
            fa[F_x] = F_y ;
            return true ;
        }
    private :
        int fa[100005] ;
}U;

void In( int t1 , int t2 , int t3 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
    p[tp].val = t3 ;
}

void kruskal(){
    sort( e + 1 , e + M + 1 ) ;
    U.init() ;
    for( int i = 1 , cnt = 0 ; i <= M ; i ++ )
        if( U.Union( e[i].u , e[i].v ) ){
            In( e[i].u , e[i].v , e[i].val ) ;
            In( e[i].v , e[i].u , e[i].val ) ;
            e[i].ontree = true ;
            sum += e[i].val ;
            //printf( "(kruskal ) %d connec with %d  val : %d\n" , e[i].u , e[i].v , e[i].val ) ;
            if( ( ++cnt ) == N - 1 ) break ;
        }
    //printf( "\n" ) ;
    ans = 1LL * 1e9 * 100005 ;//初始化无限大
}

int fa[100005][17] , maxv[100005][17] , maxblv[100005][17] , dep[100005] ;
void dfs( int u ){
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == fa[u][0] ) continue ;
        fa[v][0] = u ; dep[v] = dep[u] + 1 ;
        maxv[v][0] = p[i].val ;
        maxblv[v][0] = -1 ;
        dfs( v ) ;
    }
}

void cntmul(){
    for( int i = 1 ; i <= 16 ; i ++ ){
        for( int j = 1 ; j <= N ; j ++ ){
            fa[j][i] = fa[ fa[j][i-1] ][i-1] ;
            if( maxv[j][i-1] != maxv[ fa[j][i-1] ][i-1] ){//倍增维护细节
                maxv[j][i] = max( maxv[j][i-1] , maxv[j][i-1] ][i-1] ) ;
                if( maxv[j][i-1] > maxv[ fa[j][i-1] ][i-1] )
                     maxblv[j][i] = max( maxblv[j][i-1] , maxv[ fa[j][i-1] ][i-1] ) ;
                else maxblv[j][i] = max( maxblv[ fa[j][i-1] ][i-1] , maxv[j][i-1] ) ;
            } else {
                maxv[j][i] = maxv[j][i-1] ;
                maxblv[j][i] = max( maxblv[j][i-1] , maxblv[ fa[j][i-1] ][i-1] );
            }
        }
    }
}

template <typenam, T >
void getn( T &maxr , T &maxblr , const T &mv , const T &mblv ){
    //用链的最大值mv,次大值mblv,更新当前已经求得的最大值maxr,次大值maxblr。
    if( maxr < mv ){
        maxblr = ( mblv > maxr ? mblv : maxr ) ;
        maxr = mv ;
    } else if( maxr == mv ) maxblr = max( maxblr , mblv ) ;
      else                  maxblr = max( maxblr , mv ) ;
}

int getLca( int u , int v , int Eval ){
    if( dep[u] < dep[v] ) swap( u , v ) ;
    int t = dep[u] - dep[v] , x = 0 , maxr = -1 , maxblr = -1 ;
    while( t ){
        if( t&1 ){
            getn( maxr , maxblr , maxv[u][x] , maxblv[u][x] ) ;
            u = fa[u][x] ;
        }
        t >>= 1 ; x ++ ;
    }
    if( u == v ) 
        return Eval - ( Eval == maxr ? maxblr : maxr ) ;

    for( int i = 16 ; i >= 0 ; i -- ){
        if( fa[u][i] != fa[v][i] ){
            getn( maxr , maxblr , maxv[u][i] , maxblv[u][i] ) ;
            getn( maxr , maxblr , maxv[v][i] , maxblv[v][i] ) ;
            u = fa[u][i] ; v = fa[v][i] ;
        }
    }
    getn( maxr , maxblr , maxv[u][0] , maxblv[u][0] ) ;
    getn( maxr , maxblr , maxv[v][0] , maxblv[v][0] ) ;
    return Eval - ( Eval == maxr ? maxblr : maxr ) ;
}

void solve(){
    kruskal() ;
    fa[1][0] = 1 ; dfs( 1 ) ;
    cntmul() ;
    for( int i = 1 ; i <= M ; i ++ ){
        if( e[i].ontree ) continue ;
        ans = min( ans , sum + getLca( e[i].u , e[i].v , e[i].val ) ) ;
    }
    printf( "%lld" , ans ) ;
}

int main(){
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= M ; i ++ )
        scanf( "%d%d%d" , &e[i].u , &e[i].v , &e[i].val ) ;
    solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值