最小生成树 Boruvka算法 及例题 (CSAcademy MST and Rectangles)

求最小生成树有两种广为人知的方法,Kruskal和Prim。但是在某些特殊的情况下,比如边特别多但是边权满足一些特殊的性质,这时需要用到Boruvka算法

Boruvka的算法流程如下:一开始没加任何边的情况下,每个点都是一个独立的连通块。每一轮,对每个连通块找出连接它和另一个连通块的权值最小的边;如果加入这条边不形成环,就把它加入最小生成树。这样每一轮至少会把连通块的数量减半,所以最多logn轮就可以求出答案。如果暴力找边的话,时间复杂度是O(mlogn);但是在特殊题目中可以优化找边的复杂度,这时才能体现出Boruvka的优势。

正确性证明:咕咕咕


例题 (CS Academy - MST and Rectangles)

看清题意,是要做完m次修改后输出一次,不是每次修改都要输出。每次修改都输出是没法做的。

这题任意两点之间都可以连边,边数是n2级别的。考虑直接使用Boruvka。

假设当前Boruvka已经进行了若干轮,已经连成了一些连通块。把每个连通块内的点染成同一种独一无二的颜色。考虑对每个找出一条连到不同颜色的点的权值最小的边。i号点连到其它点的边权是由矩阵的第i行和第i列按位置相加的值决定的。由于修改是子矩阵加,我们可以用线段树扫描线,并把行和列一起处理(用同一棵线段树),也就是先进行第一行和第一列的操作,再进行第二行和第二列的操作…… 在进行到第i行/列时,线段树里存的就是点i到1~n中每个点的边权了。线段树的每个节点存4个值:区间内到i边权最小的点编号x及其颜色;以及区间内颜色与x不同的点中,到i边权最小的点编号y及其颜色。这样通过访问线段树根节点,一定可以找出到i边权最小的颜色不同的点。

Boruvka每进行一轮都需要进行一次扫描线,一共进行logn轮,所以总复杂度O(nlog2n)

点击查看代码
#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();
}
posted @   LegendStane  阅读(597)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示