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

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

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

正确性证明:咕咕咕


例题 (CS Academy - MST and Rectangles)

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

这题任意两点之间都可以连边,边数是\(n^2\)级别的。考虑直接使用Boruvka。

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

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

点击查看代码
#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 @ 2022-11-09 11:12  LegendStane  阅读(912)  评论(0)    收藏  举报