依旧拿板子题练手:【模板】最小生成树 - 洛谷
众所周知,求最小生成树有两种方法:prim和kruscal。
Prim
使用链式前向星双向存储。
维护dis数组,存储已标记点到未标记点的最短距离(上标即为未标记点的编号)。循环过程中,考虑到重边的影响,需要进行去重操作(即只留下到达某节点所有边中的最短边),后循环查找最小边、更新答案,并利用最小边到达的节点继续去重、探索下一个最小边。
#include<bits/stdc++.h>
using namespace std;
const int maxn=200005, inf=0x7fffffff;
int n,m,x,y,z,cnt,ans,dis[5005],head[5005],vis[5005];
struct edge{
int next,to,dis;
}e[maxn<<1];
void add(int from,int to,int dis){
e[++cnt].next=head[from];
e[cnt].dis=dis;
e[cnt].to=to;
head[from]=cnt;
}
int main(){
cin>>n>>m;
for(int i=0;i<m;++i){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
for(int i=2;i<=n;++i) dis[i]=inf;//dis[1]为0
int minn, tot=0, now=1;
vis[now]=1;
//假设1->2,1->3
for(int i=head[1];i;i=e[i].next)
if(dis[e[i].to]>e[i].dis)//更新2/3的距离
dis[e[i].to]=e[i].dis;//head的上标为节点编号
while(++tot<n){
minn=inf;
vis[now]=1;
for(int i=1;i<=n;++i)
if(!vis[i]&&dis[i]<minn){
minn=dis[i];//假设最短距离为dis[2]
now=i;
}
if(minn==inf){
cout<<"orz";
return 0;
}
ans+=minn;
for(int i=head[now];i;i=e[i].next)//2->4,2->5同上操作
if(dis[e[i].to]>e[i].dis&&!vis[e[i].to])
dis[e[i].to]=e[i].dis;
}
cout<<ans;
return 0;
}
Kruskal
把并查集给忘了QAQ补补:
并查集分为查找和合并。
//查找就是找最早的祖先:
int find(int x){
//while(x!=fa[x]) x=fa[x];
//上面的式子在“辈分”较早的情况下会影响查找效率
//推荐下面的路径压缩法,可以将链式祖先查找变为树状
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;
}
//合并就是让一个人的祖先认另一个人的祖先做爸爸
void merge(int x, int y){
fa[find(x)]=find(y);
}
对于生成树,被合并的相当于进入了“大部队”——所有被合并的节点有同一个祖先。
Kruskal的思想很简单:将所有边从小到大排序,后只要不形成闭环(没进入大部队),就可以加入答案。
#include<bits/stdc++.h>
using namespace std;
const int maxn=200005, inf=0x7fffffff;
int n,m,ans,cnt,fa[5005];
struct edge{
int from,to,dis;
}e[maxn<<1];
bool cmp(edge a,edge b){
return a.dis<b.dis;
}
int find(int x){
while(fa[x]!=x) x=fa[x]=fa[fa[x]];
return x;
}
int main(){
cin>>n>>m;
for(int i=0;i<m;++i){
scanf("%d%d%d",&e[i].from,&e[i].to,&e[i].dis);
}
for(int i=1;i<=n;++i) fa[i]=i;
sort(e,e+m,cmp);
for(int i=0;i<m;++i){
int x=find(e[i].from), y=find(e[i].to);
if(x==y) continue;
fa[x]=y;
ans+=e[i].dis;
if(++cnt==n-1){
cout<<ans;
return 0;
}
}
cout<<"orz";
return 0;
}