最小生成树 Boruvka算法 及例题 (CSAcademy MST and Rectangles)
求最小生成树有两种广为人知的方法,Kruskal和Prim。但是在某些特殊的情况下,比如边特别多但是边权满足一些特殊的性质,这时需要用到Boruvka算法。
Boruvka的算法流程如下:一开始没加任何边的情况下,每个点都是一个独立的连通块。每一轮,对每个连通块找出连接它和另一个连通块的权值最小的边;如果加入这条边不形成环,就把它加入最小生成树。这样每一轮至少会把连通块的数量减半,所以最多轮就可以求出答案。如果暴力找边的话,时间复杂度是;但是在特殊题目中可以优化找边的复杂度,这时才能体现出Boruvka的优势。
正确性证明:咕咕咕
例题 (CS Academy - MST and Rectangles)
看清题意,是要做完m次修改后输出一次,不是每次修改都要输出。每次修改都输出是没法做的。
这题任意两点之间都可以连边,边数是级别的。考虑直接使用Boruvka。
假设当前Boruvka已经进行了若干轮,已经连成了一些连通块。把每个连通块内的点染成同一种独一无二的颜色。考虑对每个点找出一条连到不同颜色的点的权值最小的边。i号点连到其它点的边权是由矩阵的第i行和第i列按位置相加的值决定的。由于修改是子矩阵加,我们可以用线段树扫描线,并把行和列一起处理(用同一棵线段树),也就是先进行第一行和第一列的操作,再进行第二行和第二列的操作…… 在进行到第i行/列时,线段树里存的就是点i到1~n中每个点的边权了。线段树的每个节点存4个值:区间内到i边权最小的点编号x及其颜色;以及区间内颜色与x不同的点中,到i边权最小的点编号y及其颜色。这样通过访问线段树根节点,一定可以找出到i边权最小的颜色不同的点。
Boruvka每进行一轮都需要进行一次扫描线,一共进行轮,所以总复杂度。
点击查看代码
#include <bits/stdc++.h> #define rep(i,n) for(int i=0;i<n;++i) #define repn(i,n) for(int i=1;i<=n;++i) #define LL long long #define pii pair <LL,LL> #define fi first #define se second #define mpr make_pair #define pb push_back void fileio() { #ifdef LGS freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif } void termin() { #ifdef LGS std::cout<<"\n\nPROGRAM TERMINATED"; #endif exit(0); } using namespace std; LL n,m,ans=0,fa[100010]; pii opt[100010]; vector <pair <pii,LL> > op[100010]; LL Find(LL x){return fa[x]==x ? x:fa[x]=Find(fa[x]);} void chmin(pii &x,pii y){if(x>y) x=y;} namespace st { LL n2=1,tag[400010]; pii dat[400010][2]; void addTag(LL k,LL val){tag[k]+=val;dat[k][0].fi+=val;if(dat[k][1].fi<1e9) dat[k][1].fi+=val;} void pushDown(LL k) { if(tag[k]==0) return; addTag(k+k+1,tag[k]);addTag(k+k+2,tag[k]); tag[k]=0; } void pushUp(LL k) { if(dat[k+k+1][0].fi<dat[k+k+2][0].fi) { dat[k][0]=dat[k+k+1][0];dat[k][1]=dat[k+k+1][1]; if(dat[k+k+2][0].se!=dat[k][0].se) chmin(dat[k][1],dat[k+k+2][0]);else chmin(dat[k][1],dat[k+k+2][1]); } else { dat[k][0]=dat[k+k+2][0];dat[k][1]=dat[k+k+2][1]; if(dat[k+k+1][0].se!=dat[k][0].se) chmin(dat[k][1],dat[k+k+1][0]);else chmin(dat[k][1],dat[k+k+1][1]); } } void build(LL k,LL lb,LL ub) { if(lb==ub) return; build(k+k+1,lb,(lb+ub)/2);build(k+k+2,(lb+ub)/2+1,ub); pushUp(k); } void upd(LL k,LL lb,LL ub,LL tlb,LL tub,LL val) { if(ub<tlb||tub<lb) return; if(tlb<=lb&&ub<=tub) { addTag(k,val); return; } pushDown(k); upd(k+k+1,lb,(lb+ub)/2,tlb,tub,val);upd(k+k+2,(lb+ub)/2+1,ub,tlb,tub,val); pushUp(k); } } int main() { fileio(); freopen("season.in","r",stdin); freopen("season.out","w",stdout); cin>>n>>m; LL x,y,xx,yy,z; rep(i,m) { scanf("%lld%lld%lld%lld%lld",&x,&y,&xx,&yy,&z); op[xx-1].pb(mpr(mpr(x-1,y-1),z));op[yy].pb(mpr(mpr(x-1,y-1),-z)); op[x-1].pb(mpr(mpr(xx-1,yy-1),z));op[y].pb(mpr(mpr(xx-1,yy-1),-z)); } rep(i,n) fa[i]=i; while(st::n2<n) st::n2*=2; while(true) { LL cc=0;rep(i,n) if(Find(i)==i) ++cc; if(cc==1) break; rep(i,st::n2+st::n2+3) st::dat[i][0]=st::dat[i][1]=mpr(1e9,-1),st::tag[i]=0; rep(i,n) st::dat[i+st::n2-1][0]=mpr(0,fa[i]); st::build(0,0,st::n2-1); rep(i,n) opt[i]=mpr(1e9,-1); rep(i,n) { rep(j,op[i].size()) st::upd(0,0,st::n2-1,op[i][j].fi.fi,op[i][j].fi.se,op[i][j].se); if(st::dat[0][0].se!=fa[i]) chmin(opt[fa[i]],st::dat[0][0]); else chmin(opt[fa[i]],st::dat[0][1]); } rep(i,n) if(fa[i]==i) { LL u=i,v=opt[i].se; if(Find(u)!=Find(v)) { fa[Find(u)]=Find(v); ans+=opt[i].fi; } } } cout<<ans<<endl; termin(); }
分类:
笔记
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App