次小生成树学习笔记
严格次小生成树
前置芝士
定义
如果最小生成树选择的边集是
,严格次小生成> 树选择的边集是 ,那么需要满足:( 表示边 的权值) 。
也就是说,严格次小生成树的边权和一定是比最小生成树的边权和要小,且是其它生成树中最大的。
题目
我们先来看一道例题
P4180 [BJWC2010] 严格次小生成树
求出无向图中的严格次小生成树
数据中无向图不保证无自环。
对于的数据, , ,边权 ,数据保证必定存在严格次小生成树。
解决办法
题目中
但是我们仔细思考一下,严格次小生成树或许就是最小生成树中删去某一条边再添加另一条边得到的。
正向枚举删掉的边貌似不好处理,我们可以反向枚举添加的那条边。
那我们该怎么判断删掉那条边呢?仔细思考,我们可以得知一个信息:添加一条边后,这棵树就出现了环,我们只要删去环上的权值最大的一条边即可。
但是这样还会出现一个问题,就是如果最大的边和添加的这条边边权相同怎么办?(如图)。
我们要加上
该怎么解决呢?其实我们可以再维护一个环上的严格次大的边权,这样即使最大边权与新加入的边的边权一样,也可以将严格次大的边与新加入的边替换。在上图中应该将
但是又有一个问题出现了,光靠暴力维护环上的边权是不行的,时间复杂度达到了
这时候我们可以在最小生成树上使用 (
维护严格次大值也是照葫芦画瓢,具体看代码。
解决步骤
- 输入。
- 求出图的最小生成树。
预处理出最小生成树中 时需要用的 数组和边权最大、严格次大值。- 枚举添加的边(暂且将其记为
),将 的两个端点的 求出,并求出边权最大和严格次大值,并判断出该用哪条边替换,最终求出最小值。
代码
#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 ;
}
结语
第一次写这种文章,如果有问题,请指出,谢谢!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效