写在前面
一定要三思而后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() ;
}