Borůvka 算法
详解Borůvka 算法的本质是一种多路 Prim 最小生成树算法,复杂度 \(m\log n\),但劣于 Kruskal 的 \(\log\)
算法功能:求简单图的最小生成树
算法流程是这样的
考虑当前的图(未连边),一定由若干连通块构成,我们考虑连接连通块
可以想到,对于任意一个连通块,一定应该与尽可能优的连通块连边,并且,如果该连通块不在本操作连边,无论以后的操作如何改变其他连通块的状态,连通块总是单调不减的,花费也一定是单调不减的,因此直接在本操作连边即可
根据上述原理,可以尝试枚举所有边,然后尝试用这条边去更新端点所在连通块,对于连通块,则选择一个最优的边来更新自身
可以想到在一次操作中,总有至少一半的连通块被连接而消失,复杂度 \(m\log n\)
需要注意的有以下几点
[*]“最优的边” 在这里必须是严格的,即你需要保证不存在 \(i,j\in \),使得两条边 \(e_i=e_j\),至于为什么要这么做,一个经典的例子是重边,如果现在有 \((1,2,w=1)\) 和 \((2,1,w=1)\) 两条边,这两条边使得连通块 \(1\) 和连通块 \(2\) 都能被更新到,这样在合并的时候会连出环来,保证严格的最优性一般采用编号做第二关键字的办法
[*]当然,你在无向图上跑最小生成树也会连出环,这个时候的解决办法是你在第二个循环里合并连通块之前判一下
Borůvka 需要判的东西比较多,注意不要漏掉
P3366 【模板】最小生成树
#include<bits/stdc++.h>using namespace std;int n,m;int best;struct edge{ int from,to,w; int id; bool used; bool operator <(const edge&A)const{ if(w==A.w) return id<A.id; return w<A.w; }};vector<edge>e={{0,0,0,0,true}};struct dsu{ int fa; void clear(){ for(int i=1;i<=n;++i){ fa=i; } } int find(int id){ if(id==fa) return id; return fa=find(fa); } bool samefa(int x,int y){ int fx=find(x),fy=find(y); return fx==fy; } bool join(int x,int y){ int fx=find(x),fy=find(y); if(fx==fy) return false; fa=fy; return true; }}d;int main(){ ios::sync_with_stdio(false); cin>>n>>m; for(int i=1;i<=m;++i){ int x,y,z; cin>>x>>y>>z; e.push_back({x,y,z,i,false}); } d.clear(); int ans=0,tot=0; while(1){ bool flag=false; memset(best,0,sizeof best); for(edge i:e){ if(i.used or d.samefa(i.from,i.to)) continue; int tmp=d.find(i.from); int tmp2=d.find(i.to); if(best==0 or i<e]){ best=i.id; } if(best==0 or i<e]){ best=i.id; } } for(int i=1;i<=n;++i){ if(d.find(i)==i){ if(best!=0 and e].used==false){ e].used=true; ans+=e].w; tot++; d.join(e].from,e].to); flag=true; } } } if(!flag) break; } if(tot!=n-1) cout<<"orz"; else cout<<ans;}使用
显然,Borůvka 在稠密图上的表现不如 Prim,在稀疏图上的表现不如 Kruskal
那要这玩意有什么用吗
是因为 Borůvka 适用于一类特殊条件
这类特殊条件形如 给你一个完全图,完全图上的边权可以通过端点的点权经过某种计算得出,求最小生成树
这样的条件充分利用了 Borůvka 只会合并 \(\log n\) 次的性质,这是其他两个最小生成树算法做不到的
但是这并不意味着你套模板就行了,暴力 Borůvka 仍然在 \(n^2\log\) 级别,需要一些有性质的图来优化算法(一般是快速找到最小边权)
星际联邦
完全图上每个点有点权 \(a_i\),定义 \((u,v)(u\lt v)\) 的边权为 \(a_v-a_u\),求最小生成树
(大概绿-蓝)
我们在每一轮需要找到这个点向外到另一个联通块内的最小边。注意到当 \(i\) 固定时,最小边要么是前缀 \(\) 内的后缀最小值取到的。我们只需要对每个前缀维护最大值,以及和最大值不在同一个联通块内的最大值,后缀同理,就可以快速求出该联通块向外的最小边
时间复杂度为 \(O(n \log n)\)
#include<bits/stdc++.h>using namespace std;const long long inf=0x3f3f3f3f3f3f3f3f;int n;int a;struct val_t{ long long val,pos; inline bool operator<(const val_t&A)const{ return val<A.val; } inline bool operator>(const val_t&A)const{ return val>A.val; } inline bool operator!=(const val_t&A)const{ return pos!=A.pos; }};inline val_t max(val_t a,val_t b){ return a>b?a:b;}inline val_t min(val_t a,val_t b){ return a<b?a:b;}struct pairval_t{ val_t x,y;};const val_t pos_inf={inf,0};const val_t neg_inf={-inf,0};inline pairval_t max(pairval_t a,pairval_t b){ pairval_t ans={max(a.x,b.x),neg_inf}; if(a.x!=ans.x and a.x>ans.y) ans.y=a.x; if(a.y!=ans.x and a.y>ans.y) ans.y=a.y; if(b.x!=ans.x and b.x>ans.y) ans.y=b.x; if(b.y!=ans.x and b.y>ans.y) ans.y=b.y; return ans;}inline pairval_t min(pairval_t a,pairval_t b){ pairval_t ans={min(a.x,b.x),pos_inf}; if(a.x!=ans.x and a.x<ans.y) ans.y=a.x; if(a.y!=ans.x and a.y<ans.y) ans.y=a.y; if(b.x!=ans.x and b.x<ans.y) ans.y=b.x; if(b.y!=ans.x and b.y<ans.y) ans.y=b.y; return ans;}struct pairval_t maxn,minn;struct val_t val;inline val_t askmax(int x,int pos){ return (maxn.x.pos==pos)?maxn.y:maxn.x;}inline val_t askmin(int x,int pos){ return (minn.x.pos==pos)?minn.y:minn.x;}struct dsu{ int fa; inline void clear(){ for(int i=1;i<=n;++i){ fa=i; } } int operator[](int id){ if(id==fa) return id; return fa=this->operator[](fa); }}d;inline long long Roukusaka(){ long long ans=0; d.clear(); for(int i=1;i<=n;++i){ maxn.x={a,i}; maxn.y=neg_inf; minn.x={a,i}; minn.y=pos_inf; } for(int i=2;i<=n;++i){ maxn=max(maxn,maxn); } for(int i=n-1;i>=1;--i){ minn=min(minn,minn); } while(1){ bool flag=false; for(int i=1;i<=n;++i){ val=pos_inf; } for(int i=1;i<=n;++i){ int now=d; val_t p=askmin(i,now); val_t q=askmax(i,now); if(q.val!=-inf and a-q.val<=val.val){ val={a-q.val,q.pos}; } if(p.val!=inf and p.val-a<=val.val){ val={p.val-a,p.pos}; } } for(int i=1;i<=n;++i){ if(d==i){ if(d.pos]==i or val.val==inf) continue; d.fa.pos]]=i; ans+=val.val; flag=true; } } for(int i=1;i<=n;++i){ maxn.x={a,d}; maxn.y=neg_inf; minn.x={a,d}; minn.y=pos_inf; } for(int i=2;i<=n;++i){ maxn=max(maxn,maxn); } for(int i=n-1;i>=1;--i){ minn=min(minn,minn); } if(!flag) break; } return ans;}int main(){ ios::sync_with_stdio(false); cin>>n; for(int i=1;i<=n;++i){ cin>>a; } cout<<Roukusaka();}CF888G Xor-MST
完全图上每个点有点权 \(a_i\),定义 \((u,v)(u\neq v)\) 的边权为 \(a_u \operatorname{xor} a_v\),求最小生成树
考虑放到 trie 树上维护异或和最值
码量还行,找个时间码了
(紫)
图
给定两颗带权无向树 \(T_1,T_2\),定义 \(dis_i(x,y)\) 表示树 \(T_i\) 上 \(x,y\) 间的距离,现有一完全二分图,左部,右部分别有 \(n\) 个点,定义左部点 \(i\) 与右部点 \(j\) 之间的边权为 \(\max\limits_{x=1}^n(dis_1(i,x)+dis_2(j,x))\),求完全二分图最小生成树
https://h.hszxoj.com/d/hztg/contest/6716222721518607d314c04f/file/graph.cpp
页:
[1]