最小生成树相关技术

注意只有连通图才有生成树,图不连通就只有生成森林。

无向图最小生成树

都利用了贪心的思想。一些看似不是最小生成树的问题想想贪心的话或许能化归过来。

Kruskal

基本思想是按边权从小到大加边,是贪心思想。

时间复杂度\(O(m\log m)\)

板子
sort(e+1,e+tot+1,cmp);
for(int i=1;i<=tot;++i){
	int u=e[i].u,v=e[i].v;
	u=find(u),v=find(v);
	if(u==v) continue;
	fa[v]=u;
	ans+=g[i].w;
	if(--n<2) break;//总共只能加入n-1条边
}

Prim

基本思想是从一个点开始,不断加点。

具体而言,每次找距离已经确定的最小生成树部分最小的点加入最小生成树,并更新其他点到已确定的最小生成树部分的距离。

暴力的时间复杂度为\(O(n^2+m)\),堆优化的时间复杂度为\(O((n+m)\log n)\)

暴力板子
#include<bits/stdc++.h>

using namespace std;

const int maxn=5e3+10,maxm=2e5+10,inf=1e9+7;
int n,m,tot,ans,head[maxn],dis[maxn];
bool vis[maxn];
struct edge{
	int v,w,nxt;
}e[maxm<<1];

void add(int u,int v,int w){
	e[++tot].v=v;
	e[tot].w=w;
	e[tot].nxt=head[u];
	head[u]=tot;
}

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) dis[i]=inf;
	dis[1]=0;
	for(int i=1;i<=m;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);
		add(v,u,w);
	}
	queue<int> q;
	q.push(1);
	vis[1]=true;
	ans=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(vis[v]) continue;
			vis[v]=true;
			q.push(v);
			ans++;
		}
	}
	if(ans!=n){
		puts("orz");
		exit(0);
	}
	ans=0;
	for(int i=1;i<=n;++i) vis[i]=false;
	for(int i=1;i<=n;++i){
		int mini=inf,u=0;
		for(int j=1;j<=n;++j){
			if(dis[j]<mini&&!vis[j]) mini=dis[j],u=j;
		}
		ans+=mini;
		vis[u]=true;
		for(int j=head[u];j;j=e[j].nxt){
			int v=e[j].v,w=e[j].w;
			if(dis[v]>w) dis[v]=w;
		}
	}
	printf("%d\n",ans);
	return 0;
}
堆优化Prim板子
void Prim() {
  memset(dis,0x3f,sizeof(dis));
  dis[1]=0;
  q.push({1,0});
  while(!q.empty()){
    if(cnt>=n) break;
    int u=q.top().u,d=q.top().d;
    q.pop();
    if(vis[u]) continue;
    vis[u]=1;
    ++cnt;
    res+=d;
    for (int i=h[u];i;i=e[i].x){
      int v=e[i].v,w=e[i].w;
      if(w<dis[v]) dis[v]=w,q.push({v,w});
    }
  }
}

Boruvka

某B开头的神秘MST算法。

在处理完全图上/边有很多特殊性质的MST时,Boruvka的思想很好用(虽然我们一般不会真的去写它)。

主要思想是每次找到每个连通块连上的最小的边,把它加入边集,然后不断重复,直到只有一个连通块。

初始时每个点都被视作独立的联通块。

就酱。

还是放个板子,说不定什么时候真的要写这玩意了。

点击查看代码
#include<bits/stdc++.h>

using namespace std;
bool Mbe;

constexpr int maxn=5010,maxm=2e5+10,inf=1e9;

int n,m,U[maxm],V[maxm],w[maxm],fa[maxn];

int dis[maxn],p[maxn];
bool vis[maxm];

int findf(int u){
	return u==fa[u]?u:fa[u]=findf(fa[u]);
}

bool Med;
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	
	cin>>n>>m;
	for(int i=1;i<=m;++i) cin>>U[i]>>V[i]>>w[i];
	
	for(int i=1;i<=n;++i) fa[i]=i;
	int c=n;
	for(int i=1;i<=m;++i){
		if(findf(U[i])!=findf(V[i])){
			fa[findf(U[i])]=findf(V[i]);
			c--;
		}
	}
	
	if(c>1){
		cout<<"orz"<<'\n';exit(0);
	}
	
	for(int i=1;i<=n;++i) fa[i]=i;
	c=n;
	int ans=0;
	while(true){
		for(int i=1;i<=n;++i) dis[i]=inf,p[i]=-1;
		for(int i=1;i<=m;++i){
			int fu=findf(U[i]),fv=findf(V[i]);
			if(vis[i]||fu==fv) continue;
			if(w[i]<dis[fu]) dis[fu]=w[i],p[fu]=i;
			if(w[i]<dis[findf(V[i])]) dis[fv]=w[i],p[fv]=i;
		}
		for(int i=1;i<=n;++i){
			if(p[i]==-1) continue;
			int fu=findf(U[p[i]]),fv=findf(V[p[i]]);
			if(fu==fv||vis[p[i]]) continue;
			fa[fu]=findf(fv);
			ans+=dis[i];c--;vis[p[i]]=true;
		}
		if(c==1) break;
	}
	
	cout<<ans<<'\n';
	
	cerr<<'\n'<<fabs(&Med-&Mbe)/1048576.0<<"MB\n";
	cerr<<'\n'<<1e3*clock()/CLOCKS_PER_SEC<<"ms\n";
	return 0;
}

相关的技术

Kruskal重构树

我们在Kruskal的过程中建一棵树出来。

Kruskal从小到大加边,每次加边会合并两个集合,我们新建一个点,点权设为边权,左右儿子设为两个集合的根,然后把两个集合合并,以新建节点为根。

很好地描述了求最小生成树过程中边权限制下的连通性。

性质

原图上的每个点都是叶子,点权为\(0\)

原图上两点间所有简单路径上最大边权的最小值=最小生成树上两点间的简单路径上的最大边权=Kruskal重构树上两点LCA的权值。

从一个点\(x\)到根的路径上的点权是单增的。

到点\(x\)的简单路径上最大边权的最小值\(\le val\)的点都在\(x\)到根的链上的某个点的子树内,且为该子树的所有叶子节点,且这个点满足点权\(\le val\)且最浅的。

要是将Kruskal的过程换成从大到小加边,则性质是类似的,“最大的最小”会变成“最小的最大”,其他推一下,其实很一眼。

把重构树建出来后就以解决树上问题的方式做。

P1967 [NOIP2013 提高组] 货车运输

点权多叉重构树

方法一

给每条边赋权,同时限制两个端点的点权,然后变成普通的重构树。

方法二

我们发现很少用到重构树是二叉树的性质,考虑只使用原图上的点,建出多叉树来减小常数。

Alex_Wei 的博客

不妨设限制路径上的最大值。按点权从小到大把点排序,枚举一个点 \(u\),遍历其出边指向的 \(v\),如果 \(v\) 被遍历过,说明 \(w_u\ge w_v\)。如果此时 \(u,v\) 不连通,\(u\)\(v\) 的代表元连边即可。

需要记住,二叉树的性质在这里被扔掉了,但是如果需要的话,任意一种重构树都可以是二叉树。

有向图最小生成树

“有向图最小生成树”

其实不是真正的有向图最小生成树(这玩意其实很冷门)。仔细思考一下即可发现端倪。所以一般是无向图上最小生成树算法中贪心思想的变体,考虑贪心就好。

P2573 [SCOI2012] 滑雪

真正的有向图最小生成树算法

朱刘算法

Tarjan

从未见过的全新MST

度限制下最小生成树

Q:限制\(u\)在MST中的度数必须为\(k\),求最小生成树。

Sol:

是wqs二分。考虑二分给每条\(u\)相连的边加上一个偏移量。

posted @ 2024-12-23 10:49  RandomShuffle  阅读(31)  评论(0)    收藏  举报