[老题新解]LCT维护最小生成树
前言#
之前见到的一道题,现在有了更优秀的解法。
题意#
有一个 个点的无向图,初始没有边,每次加入一条带权边后询问最小生成树权值,无生成树输出 0
,加边共计 次。
。
解法#
如果对于每次询问都直接做 Prim 最小生成树是可以通过这道题的,但是人要有追求,考虑如何通过更大的数据。
现在考虑求生成树的过程,当加入了一条边的时候有两种可能:
- 这条边连接的两点不在一棵树内,则这条边立即被选中
- 这条边连接的两点在一棵树内,需要分类讨论
现在着重讨论 2. 的情况,令这两个点为 ,因为现在 在同一棵树上,那么 之间仅有一条简单路径,此时尝试用新的边替换掉 路径上边权最大的边,如果路径上边权都比新边小,那没有必要加入这条边。
于是我们需要实现动态加边,删边,查询路径最小值的数据结构,这里使用 Link-Cut Tree 维护。
但是由于 LCT 的辅助树结构本身不支持直接维护边权,我们把每条边作为一个点维护,边权就变成了点权。即对于加边 ,首先建立一个新的点 点权为 ,再连 两条边即可。
代码#
#include <cstdio>
#include <cstring>
#include <algorithm>
constexpr int N = 50005;
struct LinkCutTree {
bool rev[N];
int fa[N],ch[N][2];
int val[N],mx[N];
#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])
#define dir(x) (x == ch[fa[x]][1])
#define IsRoot(x) (x != ch[fa[x]][0] && x != ch[fa[x]][1])
inline void PushUp(int x) {
mx[x] = x;
if(ls(x) && val[mx[ls(x)]] > val[mx[x]]) mx[x] = mx[ls(x)];
if(rs(x) && val[mx[rs(x)]] > val[mx[x]]) mx[x] = mx[rs(x)];
}
inline void PushDown(int x) {
if(rev[x]) {
if(ls(x)) std::swap(ls(ls(x)),rs(ls(x))),rev[ls(x)] ^= 1;
if(rs(x)) std::swap(ls(rs(x)),rs(rs(x))),rev[rs(x)] ^= 1;
rev[x] = 0;
}
}
void update(int x) {
if(!IsRoot(x)) update(fa[x]);
PushDown(x);
}
inline void rotate(int x) {
int y = fa[x],z = fa[y],d = dir(x),w = ch[x][d ^ 1];
if(!IsRoot(y)) ch[z][dir(y)] = x;
ch[y][d] = w,ch[x][d ^ 1] = y;
fa[y] = x,fa[x] = z;
if(w) fa[w] = y;
PushUp(y);
}
inline void splay(int x) {
update(x);
while(!IsRoot(x)) {
int y = fa[x],z = fa[y];
if(!IsRoot(y))
rotate((ls(y) == x) ^ (ls(z) == y) ? x : y);
rotate(x);
}
PushUp(x);
}
inline void access(int x) {
for(int p = 0;x;p = x,x = fa[x])
splay(x),rs(x) = p,PushUp(x);
}
inline void MakeRoot(int x) {
access(x),splay(x);
std::swap(ls(x),rs(x)),rev[x] ^= 1;
}
inline int FindRoot(int x) {
access(x),splay(x);
while(ls(x)) PushDown(x),x = ls(x);
splay(x);
return x;
}
inline void split(int x,int y) {
MakeRoot(x),access(y),splay(y);
}
inline void link(int x,int y) {
MakeRoot(x); fa[x] = y;
}
}T;
int main() {
int n,m,ecnt = 0;
scanf("%d%d",&n,&m);
int res = 0;
for(int i = 1;i <= m;++i) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
if(u == v) continue;
T.val[i + n] = w;
T.MakeRoot(u);
if(u != T.FindRoot(v)) {
T.link(i + n,u),T.link(i + n,v);
++ecnt;
res += w;
}
else {
T.split(u,v);
int ep = T.mx[v];
if(w < T.val[ep]) {
T.splay(ep);
T.fa[T.ch[ep][0]] = T.fa[T.ch[ep][1]] = 0;
T.link(i + n,u);
T.link(i + n,v);
res -= T.val[ep];
res += w;
}
}
if(ecnt == n - 1) printf("%d\n",res);
else puts("0");
}
return 0;
}
更多应用#
最小差值生成树#
求一个最小边权与最大边权差值最小的生成树。
首先将边排序,从小到大加边。每次选出链上最小边替换掉,然后更新最小差值。
严格次小生成树#
求一个严格次于最小生成树的次小生成树权值。
首先建立最小生成树然后维护链上最大值和严格次大值,对于每条不被选中的边 查询其替换掉最小生成树 这条链上最大值的生成树权值,如果和最小生成树权值相等再尝试替换严格次大值。
魔法森林#
一个无向图每条边边有两个权值 ,从一点 到另一点 需要的权值是路径上 ,求从 到 所需最小权值。
首先把边按照 权值升序排序,动态维护 权值的最小生成树,每次加边后如果 已经在一棵树内就更新最小值。
作者:AstatineAi
出处:https://www.cnblogs.com/AstatineAi/p/LCT-MST.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本