Borůvka 演算法

HaneDaniko發表於2024-11-09

詳解

Borůvka 演算法的本質是一種多路 Prim 最小生成樹演算法,複雜度 \(m\log n\),但劣於 Kruskal 的 \(\log\)

演算法功能:求簡單圖的最小生成樹

演算法流程是這樣的

考慮當前的圖(未連邊),一定由若干連通塊構成,我們考慮連線連通塊

可以想到,對於任意一個連通塊,一定應該與儘可能優的連通塊連邊,並且,如果該連通塊不在本操作連邊,無論以後的操作如何改變其他連通塊的狀態,連通塊總是單調不減的,花費也一定是單調不減的,因此直接在本操作連邊即可

根據上述原理,可以嘗試列舉所有邊,然後嘗試用這條邊去更新端點所在連通塊,對於連通塊,則選擇一個最優的邊來更新自身

可以想到在一次操作中,總有至少一半的連通塊被連線而消失,複雜度 \(m\log n\)

需要注意的有以下幾點

  • “最優的邊” 在這裡必須是嚴格的,即你需要保證不存在 \(i,j\in [1,m]\),使得兩條邊 \(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[100001];
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[100001];
    void clear(){
        for(int i=1;i<=n;++i){
            fa[i]=i;
        }
    }
    int find(int id){
        if(id==fa[id]) return id;
        return fa[id]=find(fa[id]);
    }
    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[fx]=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[tmp]==0 or i<e[best[tmp]]){
                best[tmp]=i.id;
            }
            if(best[tmp2]==0 or i<e[best[tmp2]]){
                best[tmp2]=i.id;
            }
        }
        for(int i=1;i<=n;++i){
            if(d.find(i)==i){
                if(best[i]!=0 and e[best[i]].used==false){
                    e[best[i]].used=true;
                    ans+=e[best[i]].w;
                    tot++;
                    d.join(e[best[i]].from,e[best[i]].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\) 固定時,最小邊要麼是字首 \([1, i)\) 的最大值取到的,要麼是 \((i, n]\) 內的字尾最小值取到的。我們只需要對每個字首維護最大值,以及和最大值不在同一個聯通塊內的最大值,字尾同理,就可以快速求出該聯通塊向外的最小邊

時間複雜度為 \(O(n \log n)\)

#include<bits/stdc++.h>
using namespace std;
const long long inf=0x3f3f3f3f3f3f3f3f;
int n;
int a[300005];
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[300001],minn[300001];
struct val_t val[300001];
inline val_t askmax(int x,int pos){
    return (maxn[x].x.pos==pos)?maxn[x].y:maxn[x].x;
}
inline val_t askmin(int x,int pos){
    return (minn[x].x.pos==pos)?minn[x].y:minn[x].x;
}
struct dsu{
    int fa[300001];
    inline void clear(){
        for(int i=1;i<=n;++i){
            fa[i]=i;
        }
    }
    int operator[](int id){
        if(id==fa[id]) return id;
        return fa[id]=this->operator[](fa[id]);
    }
}d;
inline long long Roukusaka(){
	long long ans=0;
    d.clear();
	for(int i=1;i<=n;++i){
		maxn[i].x={a[i],i};
        maxn[i].y=neg_inf;
		minn[i].x={a[i],i};
        minn[i].y=pos_inf;
	}
	for(int i=2;i<=n;++i){
        maxn[i]=max(maxn[i-1],maxn[i]);
    }
	for(int i=n-1;i>=1;--i){
        minn[i]=min(minn[i+1],minn[i]);
    }
	while(1){
        bool flag=false;
		for(int i=1;i<=n;++i){
            val[i]=pos_inf;
        }
		for(int i=1;i<=n;++i){
			int now=d[i];
			val_t p=askmin(i,now);
            val_t q=askmax(i,now);
			if(q.val!=-inf and a[i]-q.val<=val[now].val){
                val[now]={a[i]-q.val,q.pos};
            }
			if(p.val!=inf and p.val-a[i]<=val[now].val){
                val[now]={p.val-a[i],p.pos};
            }
		}
		for(int i=1;i<=n;++i){
			if(d[i]==i){
                if(d[val[i].pos]==i or val[i].val==inf) continue;
                d.fa[d[val[i].pos]]=i;
                ans+=val[i].val;
                flag=true;
            }
		}
		for(int i=1;i<=n;++i){
			maxn[i].x={a[i],d[i]};
            maxn[i].y=neg_inf;
			minn[i].x={a[i],d[i]};
            minn[i].y=pos_inf;
		}
		for(int i=2;i<=n;++i){
            maxn[i]=max(maxn[i-1],maxn[i]);
        }
		for(int i=n-1;i>=1;--i){
            minn[i]=min(minn[i+1],minn[i]);
        }
        if(!flag) break;
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(false);
    cin>>n;
	for(int i=1;i<=n;++i){
        cin>>a[i];
    }
	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

相關文章