最小生成树

最小生成树

我们定义无向连通图的 最小生成树(Minimum Spanning Tree,MST)为边权和最小的生成树。
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。

两种方法:

Kruskal算法

适用于题目数据规模小,边不那么多的情况
以边作为出发点,对于所有的,每次判断权值最小的边是否在MST中,若不在则加进去
利用并查集判断是否在MST中,利用优先队列或者结构体排序的方式实现

dsu板子,用来维护并查集

struct dsu{
	vector<int> fa;
	int num;
	dsu(int x=maxm):num(x),fa(x+1){
		for(int i=0;i<=x;++i) fa[i]=i;
	}
	int findfa(int x){ return fa[x]==x? x:fa[x]=findfa(fa[x]); }
	void merge(int u,int v){
		fa[findfa(u)]=findfa(v); return ;
	}
};

洛谷 P3366 最小生成树为例

void kruskal(){
	dsu ds(n);
	edge t;
	int a,b;
	while(!q.empty()){
		if(sum_edge==n-1) return ;
		t=q.top();
		q.pop();
		a=ds.findfa(t.u);
		b=ds.findfa(t.v);
		if(a!=b){
			ans+=t.w;
			ds.merge(a,b);
			++sum_edge;//本题用于判断连通性,统计边数
		}
	}
	return ;
}

Prim算法

在遇到稠密图时表现的更加良好
以点作为出发点,先将一个初始点放入MST中,之后每次选择与已经放入MST中的点相连的距离最短的点加入MST中,以此类推
过程与dijkstra有点像,但是prim算法不需要更新所有点到起点的距离!
可以利用链式前向星存图,利用优先队列找距离最近点

void prim(){
	point=ans=0;
	priority_queue<pll,vector<pll>,greater<pll>> q;
	q.push({0,1});//{距离,点序号} 距离即为边权值
	while(!q.empty()){
		pll t=q.top();
		q.pop();
		if(vis[t.second]) continue;
		vis[t.second]=true;
		++point;//此题用来判断图的连通性,统计点数
		ans+=t.first;
		for(int i=head[t.second];i>0;i=p[i].next){
			if(!vis[p[i].to]){
				q.push({p[i].w,p[i].to});
			}
		}
	}
	return ;
}

例题

  1. https://vjudge.net/contest/565164
    21级2023暑假最小生成树训练

相关资料

  1. https://oi-wiki.org/graph/mst/

判断最小生成树是否唯一

https://oi-wiki.org/graph/mst/#最小生成树的唯一性
考虑最小生成树的唯一性。如果一条边 不在最小生成树的边集中,并且可以替换与其 权值相同、并且在最小生成树边集 的另一条边。那么,这个最小生成树就是不唯一的。

对于 Kruskal 算法,只要计算为当前权值的边可以放几条,实际放了几条,如果这两个值不一样,那么就说明这几条边与之前的边产生了一个环(这个环中至少有两条当前权值的边,否则根据并查集,这条边是不能放的),即最小生成树不唯一。

例题

判最小生成树是否唯一 The Unique MST
下为代码:

//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
//#define int long long
// typedef std::pair<int,int> pii;
// typedef std::pair<ll,ll> pll;
// typedef std::pair<ull,ull> pull;

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
	return x*f;
}

using namespace std;

const int maxm=1e4+5,inf=0x3f3f3f3f,mod=998244353;
int n,m,sum;
struct edge{
	int u,v,w;
}p[maxm];

struct dsu{
	int num;
	vector<int> fa;
	dsu(int x=maxm):num(x),fa(x+1){
		for(int i=0;i<=x;++i) fa[i]=i;
	}
	int findfa(int x){ return fa[x]==x?x:findfa(fa[x]); }
	void merge(int u,int v){
		fa[findfa(u)]=findfa(v);
		return ;
	}
};

bool kruskal(){
	bool f=false;
	sort(p,p+m,[](edge x,edge y){
		return x.w<y.w;
	});
	dsu ds(n);
	for(int i=0;i<m;++i){
		queue<edge> q;
		int len=p[i].w;
		while(i<m && len==p[i].w){
			if(ds.findfa(p[i].u)!=ds.findfa(p[i].v))
				q.push(p[i]);
			++i;
		}
		--i;
		while(!q.empty()){
			edge t=q.front();
			q.pop();
			if(ds.findfa(t.u)!=ds.findfa(t.v)){
				sum+=t.w;
				ds.merge(t.u,t.v);
			}else{
				return true;
			}
		}
	}
	return f;
}

void solve(){
	cin>>n>>m;
	for(int i=0;i<m;++i){
		cin>>p[i].u>>p[i].v>>p[i].w;
	}
	sum=0;
	if(kruskal()) cout<<"Not Unique!\n";
	else cout<<sum<<'\n';
	return ;
}

signed main(){
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}


最大生成树

与最小生成树类似,取小改为取大即可

例题

最大生成森林 洛谷 P2121 拆地毯
注意题目表述:“任意可互相到达的两点间只能有一种方式互相到达”。故我们只需要对整个图求最大生成森林,取前k条边即可

最大生成树变式:价值是边的权值位与的结果 hdu 5627 Clarke and MST
这题让你求一棵边数为n-1的生成树,树的价值为边权值的位与后的结果,问你最大的价值是多少?
两个数位与,与顺序无关。故我们要尽可能将二进制高位留下。其实最终的问题就转化为了求最大生成树?还待理解。

posted on 2023-06-26 08:43  Qiansui  阅读(9)  评论(0编辑  收藏  举报