最小生成树
最小生成树
我们定义无向连通图的 最小生成树(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 ; } };
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 ; }
例题
- https://vjudge.net/contest/565164
21级2023暑假最小生成树训练
相关资料
判断最小生成树是否唯一
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的生成树,树的价值为边权值的位与后的结果,问你最大的价值是多少?
两个数位与,与顺序无关。故我们要尽可能将二进制高位留下。其实最终的问题就转化为了求最大生成树?还待理解。
本文来自博客园,作者:Qiansui,转载请注明原文链接:https://www.cnblogs.com/Qiansui/p/17504428.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战