当前位置: 首页 > news >正文

一种另类的最小生成树(MST)算法:Boruvka算法

一种另类的最小生成树(MST)算法:Boruvka算法

这种算法是结合了Prim和Kruscal算法,适合于完全图或者稠密图。

具体思路是不断合并连通块:

  1. 初始每个点为一个连通块;
  2. 我们需要找到每个连通块到其他所有连通块的最小出边;
  3. 合并连通块,将边权统计进答案;

这个合并连通块的轮数不会超过log ⁡ n \log{n}logn,这是显然的,那么复杂度主要在每一轮中找到每个连通块的最小出边上,假设这个时间复杂度为O ( T ) O(T)O(T),那么总时间复杂度为O ( α ( n ) T log ⁡ n ) O(\alpha(n)T\log{n})O(α(n)Tlogn)

对于给出边的情况,可以像下面这样写:

#include <bits/stdc++.h> using namespace std; #define endl '\n' using ll=long long; struct DSU{ int n; vector<int> fa,sz; DSU(int n):n(n){ fa.resize(n+1); sz.resize(n+1); for(int i=1;i<=n;i++){ fa[i]=i; sz[i]=1; } } int find(int x){ if(x==fa[x]) return x; else return fa[x]=find(fa[x]); } bool merge(int x,int y){ int rx=find(x),ry=find(y); if(rx==ry){ return false; } if(sz[rx]>sz[ry]) swap(rx,ry); fa[rx]=ry; sz[ry]+=sz[rx]; return true; } }; void solve() { int n,m; cin >> n >> m; struct edge{ int u,v,w; }; vector<edge> e(m+1); for(int i=1;i<=m;i++){ cin >> e[i].u >> e[i].v >> e[i].w; } e[0].w=1e9; DSU dsu(n); ll ans=0; int cnt=0; bool change=true;//用于判断是否加了边,如果无法加边了,那么可能是最小生成森林 vector<int> mn(n+1);//记录每个连通块最小出边的编号 vector<bool> used(m+1,0);//判断每条边是否使用过 while(cnt<n-1&&change){ change=0; for(int i=1;i<=n;i++){ mn[i]=0; } for(int i=1;i<=m;i++){ auto[x,y,w]=e[i]; int rx=dsu.find(x),ry=dsu.find(y); if(rx==ry) continue; if(e[mn[rx]].w>w) mn[rx]=i; if(e[mn[ry]].w>w) mn[ry]=i; } for(int i=1;i<=n;i++){ int x=mn[i]; if(x&&!used[x]){ cnt++; ans+=e[x].w; dsu.merge(e[x].u,e[x].v); used[x]=1; change=1; } } } cout << ans << endl; } /* */ int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); int t = 1; // cin >> t; while (t--) { solve(); } return 0; }

时间复杂度为O ( α ( n ) m log ⁡ n ) O(\alpha(n)m\log{n})O(α(n)mlogn)

例题:

  1. J - Tree MST
  2. Problem - 888G - Codeforces
  3. E - Connecting Cities
  4. P2076 曼哈顿距离最小生成树 - 洛谷

下面我们先写一下例题2:https://codeforces.com/problemset/problem/888/G

题意:给了n nn个点,每个点的点权为a i a_iai,如果点i ii向点j jj连边,那么边权为a i ⊕ a j a_i\oplus a_jaiaj,问让所有点连通的最小代价。

sol

现在也就是给了( n 2 ) \binom{n}{2}(2n)条边,问最小生成树,显然对于这样的稠密图,我们无法使用Prim和Kruscal了,只能使用Boruvka算法。

我们按照算法步骤,可以知道难点在于求每个连通块的最小出边,假设现在我们连通块为S = { a 1 , a 2 , … , a k } S=\{a_1,a_2,\dots,a_k\}S={a1,a2,,ak},然后我们要知道S SS的最小出边,也就是求S SS中的点a i a_iaiS ‾ \overline SS中的点a j a_jaj的最小边权,因为边权w = a i ⊕ a j w=a_i\oplus a_jw=aiaj,要求某一个点在一个集合中异或的最小值,这是经典的01trie维护,这里还有个技巧,求S ‾ \overline SS可以通过全集减去S SS得到,所以我们单独用r t [ 0 ] rt[0]rt[0]来表示所有数构成的01trie,那么S ‾ \overline SS可以表示为r t [ 0 ] − r t [ S ] rt[0]-rt[S]rt[0]rt[S]得到。

所以具体步骤为:

  1. 初始化每个节点一个连通块,同时每个连通块维护一个01trie
  2. 找到每个连通块的最小出边,我们的m n [ i ] mn[i]mn[i]存的是一个p a i r pairpair,第一个表示边权,第二个表示连向的点(因为01trie返回的也是一个节点编号);
  3. 进行连边,合并01trie,统计答案;
#include <bits/stdc++.h> using namespace std; #define endl '\n' using ll=long long; const int N=2e5+10; const int M=N*50; const ll inf=1e18; int fa[N],siz[N],n,a[N]; ll ans; pair<ll,int> mn[N]; int sz[M],tot,tail[M],rt[N],ch[M][2]; int find(int x){ if(x==fa[x]) return x; else return fa[x]=find(fa[x]); } void insert(int& rt,int x,int idx){ if(!rt) rt=++tot; int now=rt; for(int i=29;i>=0;i--){ int p=x>>i&1; if(!ch[now][p]) ch[now][p]=++tot; now=ch[now][p]; ++sz[now]; } tail[now]=idx; } //v为全集,u为子集S,T为S的补集,查询x与T中的最小距离 //返回选的那个节点编号 int query(int u,int v,int x){ for(int i=29;i>=0;i--){ int p=x>>i&1; if((sz[ch[v][p]]-sz[ch[u][p]])>0){ u=ch[u][p]; v=ch[v][p]; }else{ u=ch[u][!p]; v=ch[v][!p]; } } return tail[v]; } //合并两个01trie,将u合并到v int mg(int u,int v){ if(!u||!v) return u|v; sz[v]+=sz[u]; ch[v][0]=mg(ch[v][0],ch[u][0]); ch[v][1]=mg(ch[v][1],ch[u][1]); return v; } //启发式合并 void merge(int u,int v){ u=find(u),v=find(v); if(siz[u]>siz[v]) swap(u,v); siz[v]+=siz[u]; fa[u]=v; rt[v]=mg(rt[u],rt[v]); } void solve(){ cin >> n; for(int i=1;i<=n;i++){ cin >> a[i]; } sort(a+1,a+1+n); n=unique(a+1,a+1+n)-a-1; for(int i=1;i<=n;i++){ siz[i]=1; fa[i]=i; insert(rt[i],a[i],i); insert(rt[0],a[i],i); } int cnt=0; while(cnt<n-1){ for(int i=1;i<=n;i++){ mn[i]={inf,0}; } for(int i=1;i<=n;i++){ int u=find(i); int v=query(rt[u],rt[0],a[i]); int w=a[i]^a[v]; if(mn[u].first>w){ mn[u]={w,v}; } } for(int i=1;i<=n;i++){ int x=find(i); if(x==i&&find(mn[x].second)!=x){ ans+=mn[x].first; merge(i,mn[x].second); cnt++; } } } cout << ans << endl; } /* */ int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); int t = 1; // cin >> t; while (t--) { solve(); } return 0; }
http://www.jsqmd.com/news/1082017/

相关文章:

  • 【Netty源码解读和权威指南】第69篇:Netty与gRPC——高性能RPC框架的底层网络秘密
  • 【控制工程全栈】2026年自控阀门技术架构解析与源头工厂选型指南(附总线诊断Python源码)
  • okbiye AI 写作数据分析:甩掉 SPSS 与 Python,自动生成可直接复用的 docx 实证报告
  • Destiny 2 Solo Enabler:3步实现单人游戏,告别匹配烦恼
  • 如何快速下载Fansly内容:终极Fansly下载器使用指南
  • 变系数Camassa-Holm方程小色散渐近解:类孤子与类尖峰形态分析
  • USB打印机/加密狗/工业采集卡在VMware中无法识别?一线运维团队压箱底的8步黄金复位流程
  • 视频号资源下载难题如何破解?3个核心功能带你轻松获取网络素材
  • 059、多端同步策略:CLI 加 IDE 加 Cloud 三端的工作流统一方法
  • 导出OVF前必须执行的4项安全审计 + 2项合规脱敏操作(GDPR/HIPAA双认证场景实操手册)
  • FModel终极指南:5步掌握虚幻引擎游戏资源解析技术
  • Adobe-GenP 3.0终极指南:如何快速免费激活Adobe全家桶
  • WinBtrfs:在Windows上解锁Linux下一代文件系统的完整指南
  • 乡村旅游票务系统—景谱乡村旅游点数字化管理方案
  • 060、IDE 插件调试与故障排查:扩展冲突、性能问题与日志分析
  • APP 自动化第一步就卡住?测试人必须掌握的 5 个 APP 测试 Skills
  • 【轨物洞见】光伏清洁机器人为何必须走向“清检一体化“
  • VMware用户紧急自救手册:3步识别许可风险,4套零停机迁移方案,7家已验证替代厂商深度对比
  • 一文读懂 KWDB 聚合函数:结合 SampleDB 实例详细拆解
  • ThinkPad终极风扇控制指南:TPFanCtrl2让你的笔记本性能全开
  • Blue Topaz主题完整教程:5分钟掌握Obsidian终极美化方案
  • 【稀缺首发】VMware官方未公开的磁盘类型转换限制清单:厚转精简失败率高达68%?3种安全迁移路径与回滚预案(含vCenter API调用实录)
  • 预编译防SQL注入原理详解:从数据库驱动到实战应用
  • 48V/60V 储能高压高侧采样方案|可调增益低功耗,储能电源采样芯片 FP135 参数讲解
  • 062、编写自定义 Skill:SKILL.md 规范、触发词设计与发布流程
  • 2026年B端外贸客户开发工具选型指南:含跨境魔方等适配需求分析
  • 实测20家企业贷款客户后,我发现贷款中介最缺的不是产品,而是一款真正靠谱的测额工具
  • 前缀和--原理详解与常见变式(C/C++ 实现)
  • 微电网混合控制架构的应用案例
  • 为什么92%的Eclipse老手改用IDEA后效率反降?真相藏在这43组快捷键语义差异里,立即自查!