次小生成树学习笔记

严格次小生成树

前置芝士

最小生成树|倍增LCA

定义

如果最小生成树选择的边集是 \(E_M\),严格次小生成> 树选择的边集是 \(E_S\),那么需要满足:(\(value(e)\) 表示边 \(e\) 的权值) \(\sum_{e \in E_M}value(e)<\sum_{e \in E_S}value(e)\)

也就是说,严格次小生成树的边权和一定是比最小生成树的边权和要小,且是其它生成树中最大的。

题目

我们先来看一道例题

P4180 [BJWC2010] 严格次小生成树

求出无向图中的严格次小生成树

数据中无向图不保证无自环
对于 \(100\%\) 的数据, \(N\le 10^5\)\(M\le 3\times10^5\),边权 \(\in [0,10^9]\),数据保证必定存在严格次小生成树。

解决办法

题目中 \(n\)\(m\) 的值很大,肯定不能暴力枚举出每个生成树。

但是我们仔细思考一下,严格次小生成树或许就是最小生成树中删去某一条边再添加另一条边得到的。

正向枚举删掉的边貌似不好处理,我们可以反向枚举添加的那条边。

那我们该怎么判断删掉那条边呢?仔细思考,我们可以得知一个信息:添加一条边后,这棵树就出现了环,我们只要删去环上的权值最大的一条边即可。

但是这样还会出现一个问题,就是如果最大的边和添加的这条边边权相同怎么办?(如图)。

img

我们要加上 \(4\)\(5\) 这条边,但是形成的环(就是点 \(4、5、1\) 形成的环)上的最大的最小生成树边和加入的边的边权相等,这导致删去一个再添加一个边之后这棵树还是最小生成树。

该怎么解决呢?其实我们可以再维护一个环上的严格次大的边权,这样即使最大边权与新加入的边的边权一样,也可以将严格次大的边与新加入的边替换。在上图中应该将 \(1\)\(5\) 的边删去,将蓝色边加入。

但是又有一个问题出现了,光靠暴力维护环上的边权是不行的,时间复杂度达到了 \(O(nm)\) 过不了例题。

这时候我们可以在最小生成树上使用 \(\text{LCA}\),把 \(1\) 号节点看作树根,将 \(mxval_{u,i}\) 定义为节点 \(u\) 向上跳的 \(2^i\) 的路径中的最大边权,那么 \(mxval_{u,0} = fa_{u,0}\),即点 \(u\) 的父亲,\(mxval_{u,i} = \max(mxval_{u,i - 1},mxval_{fa_{u ,i - 1},i - 1})\)(\(fa_{u , i}\)表示什么就不用我解释了吧,学了 \(\text{LCA}\) 的都知道)。

维护严格次大值也是照葫芦画瓢,具体看代码。

解决步骤

  • 输入。
  • 求出图的最小生成树。
  • \(\text{dfs}\) 预处理出最小生成树中 \(\text{LCA}\) 时需要用的 \(\text{fa}\) 数组和边权最大、严格次大值。
  • 枚举添加的边(暂且将其记为 \(\text{E}\)),将 \(\text{E}\) 的两个端点的 \(\text{LCA}\) 求出,并求出边权最大和严格次大值,并判断出该用哪条边替换,最终求出最小值。

代码

#include <bits/stdc++.h>
using namespace std ;
#define int long long
#define rep( i , l , r ) for (int i = (l) ; i <= (r) ; i++)
#define per( i , r , l ) for (int i = (r) ; i >= (l) ; i--)
int n , m ;
const int M = 3e5 + 10 ;
const int N = 1e5 + 10 ;
struct node{
	int u , v , w ;
}e[M] ;
int fa[N] ;
int Find (int x){return ((fa[x] == x) ? x : fa[x] = Find (fa[x])) ;}
bool cmp (node a , node b){
	return a.w < b.w ;
}
int ans ;
bool vis[M] ;
struct node1{
	int to ;
	int nxt ;
	int val ;
}g[M << 1] ;
int head[M << 2] ;

int tot ;
void add (int u , int v , int w){
	g[++tot] = (node1){v , head[u] , w} ;
	head[u] = tot ;
}
int mx_val[N][25] ;
int mn_val[N][25] ;
void kruskal(){
	rep (i , 1 , n) fa[i] = i ;
	int cnt = 0 ;
	sort (e + 1 , e + 1 + m , cmp) ;
	rep (i , 1 , m){
		if (cnt == n - 1) break ;
		int x = Find (e[i].u) ;
		int y = Find (e[i].v) ;
		if (x != y){
			vis[i] = 1 ;
			++cnt ;
			add (e[i].u , e[i].v , e[i].w) ;
			add (e[i].v , e[i].u , e[i].w) ;
			ans += e[i].w ;
			fa[y] = x ;
		}
		if (cnt == n - 1) break ;
	}
}
int st[N][25] ;
int dep[N] ;
void dfs (int u , int fa , int vl){
	st[u][0] = fa;
	mx_val[u][0] = vl ;
	mn_val[u][0] = -0x3f3f3f3f3f ;
	dep[u] = dep[fa] + 1 ;
	rep (i , 1 , 24){
		st[u][i] = st[st[u][i - 1]][i - 1] ;
		mx_val[u][i] = max (mx_val[u][i - 1] , mx_val[st[u][i - 1]][i - 1]) ;
		mn_val[u][i] = max (mn_val[u][i - 1] , mn_val[st[u][i - 1]][i - 1]) ;
		if (mx_val[u][i - 1] > mx_val[st[u][i - 1]][i - 1]){
			mn_val[u][i] = max (mn_val[u][i] , mx_val[st[u][i - 1]][i - 1]) ;
		}else if (mx_val[u][i - 1] < mx_val[st[u][i - 1]][i - 1]){
			mn_val[u][i] = max (mn_val[u][i] , mx_val[u][i - 1]) ;
		}
	}
	for (int i = head[u] ; i ; i = g[i].nxt){
		int v = g[i].to ;
		if (v == fa) continue ;
		dfs (v , u , g[i].val) ;
	}
}
int LCA (int u , int v){
	if (dep[u] < dep[v] ) swap ( u , v );
	for ( int i = 24 ; i >= 0 ; i-- ){
		if ( dep[st[u][i]] >= dep[v] ){
			u = st[u][i] ;
		}
	}
	if ( u == v ) return u ;
	for ( int i = 24; i >= 0 ; i-- ){
		if ( st[u][i] != st[v][i] ){
			u = st[u][i] ;
			v = st[v][i] ;
		}
	}
	return st[u][0] ;
}
int query (int u , int v , int x){
	int sum = -0x3f3f3f3f3f ;
	per (i , 24,  0){
		if (dep[st[u][i]] >= dep[v]){
			if (mx_val[u][i] != x) sum = max (sum , mx_val[u][i]) ;
			else sum = max (sum , mn_val[u][i]) ;
			u = st[u][i] ;
		}
	}
	return sum ;
}
void solve (){
	cin >> n >> m ;
	rep (i , 1 , m){
		cin >> e[i].u >> e[i].v >> e[i].w ;
	}
	kruskal() ;
	dfs (1 , 0 , 0) ;
	int res = 0x3f3f3f3f3f3f3f ;
	rep (i , 1 , m){
		if (!vis[i]){
			int u = e[i].u ;
			int v = e[i].v ;
			int fa = LCA (u , v) ;
			int x = query (u , fa , e[i].w) ;
			int y = query (v , fa , e[i].w) ;
			res = min (res , ans - max (x , y) + e[i].w) ;
		}
	}
	cout << res ;
}

signed main (){
	int _ = 1 ;
	//cin >> _ ;
	while ( _-- ){solve () ;}
	return 0 ;
}

结语

第一次写这种文章,如果有问题,请指出,谢谢!

posted @ 2024-12-09 20:39  lixhqwq  阅读(60)  评论(0)    收藏  举报