随笔 - 164  文章 - 0  评论 - 4  阅读 - 9677

最小生成树

最小生成树

我们定义无向连通图的 最小生成树(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   Qiansui  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

点击右上角即可分享
微信分享提示