P3366 【模板】最小生成树(prim+链式前向星+kruskal)
题目链接 https://www.luogu.com.cn/problem/P3366
你,作为一个模板题,我真的要恨恨地夸你几句 :)
点捏的真好,WA了N遍,这题(和队长)让我明白了一个人生道理:条条道路通罗马
TuT
Prim:——(4.13)
①选取一个点作为顶点,此点生成一个树
②找到与树连接的任意一点的最短距离,将此点纳入树中
③重新计算树与其他点最短的距离
④循环②③步,直到生成了最小生成树或无法生成树
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 int n,m;//n个点,m个边 5 long long x,y,z;//输入起点、终点、边权值 6 long long cnt;//记录边数 7 long long sum;//输出和 8 int book[5010];//标记数组 9 long long dis[200010];//记录距离树的距离最短路程 10 long long MAX=99999;//边界值 11 int maps[5010][5010];//存图 12 13 void Prim() 14 { 15 book[1]=1;//标记1已经被访问 16 17 for(register long long i=1;i<=n-1;i++) //因为1已经访问过,所以循环n-1次 18 { 19 int min=MAX; 20 int minIndex=0; 21 //寻找离树最近的点 22 for(register long long j=1;j<=n;j++) 23 { 24 if(book[j]==0&&dis[j]<min)//j没有被访问过,且i到j有边连接 25 { 26 min=dis[j]; 27 minIndex=j; 28 } 29 } 30 31 book[minIndex]=1;//标记此点已被访问 32 sum+=dis[minIndex]; 33 cnt++; 34 //如果不能联通所有点 35 if(min==MAX) break; 36 37 for(register long long j=1; j<=n; j++) 38 { //更新当前最小生成树到其他点的最小距离 39 //即:如果这点没有被访问过,而且这个点到任意一点的距离比现在到树的距离近那么更近 40 if(book[j]==0&&maps[minIndex][j]<dis[j]) 41 dis[j]=maps[minIndex][j];//就将更小值放入dis数组 42 } 43 } 44 } 45 46 int main() 47 { 48 std::ios::sync_with_stdio(false); 49 cin>>n>>m; 50 51 //初始化maps,除了自己到自己的点为0,其余为边界值 52 for(register long long i=1; i<=n; i++) 53 { 54 for(register int j=1; j<=n; j++) 55 { 56 if(i==j) maps[i][j]=0; 57 else maps[i][j]=MAX; 58 } 59 } 60 61 //输入无向图 62 for(register long long i=1; i<=m; i++) 63 { 64 cin>>x>>y>>z; 65 if(z<maps[x][y])//条条大路通罗马 66 { //无向图,对称矩阵 67 maps[x][y]=z; 68 maps[y][x]=z; 69 } 70 } 71 72 //初始化距离数组,默认从顶点1开始计算,并把1能够到达的点放进dis数组 73 for(register long long i=1; i<=n; i++) 74 { 75 dis[i]=maps[1][i]; 76 } 77 78 Prim(); 79 //判断是否能生成 80 if(cnt>=n-1) cout<<sum<<endl; 81 else cout<<"orz"; 82 return 0; 83 }
链式前向星:——(4.14)
(一个好写的、易用的邻接表)
这个理解了好久,一直在看文字搞不懂head数组的含义,也不明白加边加在什么地方、怎么加。后来看了个视频讲解就懂了https://www.bilibili.com/video/BV1mJ411S7BB?spm_id_from=333.337.search-card.all.click
给当时的我解答困惑:
比如有一条路是这样连接的:a->b->c->NULL
①加边加在每一条通路的最前面,也就是链表的头部,a的前面
②链式前向星的输入和遍历顺序是相反的,遍历的时候从c开始遍历
③head数组用来记录以i为起点在数组中的第一位存储位置,方便进行①操作。head数组初始化为0(或-1)
1 #include<bits/stdc++.h> 2 #define maxn 5005//顶点 3 #define maxm 200005//边 4 #define MAX 99999 5 using namespace std; 6 7 int head[maxm];//记录以i为起点在数组中的第一位存储位置,方便在第一位之前插入 8 int dis[maxm];//记录最短距离 9 int tot;//记录读到第几条边 10 int n,m;//点、边 11 int u,v,w;//起点、终点、权值 12 int ans,cnt;//最小生成树的各边长度和/已生成cnt条边 13 bool vis[maxn];//标记 14 15 struct edge 16 { 17 int v; 18 int w; 19 int next;//与第i条边同起点的下一条存储位置 20 } e[maxm<<1]; //无向图,开两倍数组 21 22 //加边 23 void add(int u,int v,int w) 24 { 25 e[++tot].v=v; 26 e[tot].w=w; 27 e[tot].next=head[u];//与下一句合并在一起是链表的插入操作 28 head[u]=tot; 29 } 30 31 int Prim() 32 { 33 int now=1;//假设从顶点1开始计算 34 for(int i=2; i<=n; i++) 35 dis[i]=MAX; 36 //条条道路通罗马 37 for(int i=head[1]; i!=0; i=e[i].next) 38 dis[e[i].v]=min(dis[e[i].v],e[i].w); 39 40 while(++cnt<n) 41 { 42 int minn=MAX; 43 vis[now]=1; 44 45 //找没有被标记的点,找出最小值最为新边 46 for(int i=1; i<=n; i++) 47 { 48 if(!vis[i]&&minn>dis[i]) 49 { 50 minn=dis[i]; 51 now=i; 52 } 53 } 54 ans+=minn; 55 56 //如果不能联通所有点 57 if(minn==MAX) break; 58 59 //更新 60 for(int i=head[now]; i!=0; i=e[i].next) 61 { 62 int v=e[i].v; 63 if(!vis[v]&&dis[v]>e[i].w) 64 dis[v]=e[i].w; 65 } 66 } 67 return ans; 68 } 69 70 int main() 71 { 72 cin>>n>>m; 73 for(int i=1; i<=m; i++) 74 { 75 cin>>u>>v>>w; 76 add(u,v,w); 77 add(v,u,w);//无向图 78 } 79 Prim(); 80 if(cnt>=n-1) cout<<ans<<endl; 81 else cout<<"orz"; 82 return 0; 83 }
Kruskal:——(4.15)
在学习kruskal之前需要学习并查集。作用:判断是否形成环。
个人觉得kruskal真的会比prim好想很多.....
将所有的边排序,每次选取最小的边放入树中,当然,在放入树之前要判断两个点是否已经在同一个集合(树)中。
用kruscal算法解决这道题不需要特判是否有重边(也就是上面所说的条条道路通罗马),因为我们按照边的权值排序放入,如果a点到b点有重边且目前还未都纳入树中,那么我们会先将权值小的边放入树中,此时两个点已经全部纳入树中,当循环到重边时会跳过。
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m;//点,边 4 long long cnt,ans; 5 int s[500010];//保存父节点 6 7 struct Edge 8 { 9 int u;//起点 10 int v;//终点 11 int w;//权值 12 } edge[500010]; 13 14 bool cmp(Edge a,Edge b) 15 { 16 return a.w<b.w; 17 } 18 19 int Find(int o) 20 { 21 return s[o]==o?o:Find(s[o]); 22 } 23 24 int Kruskal() 25 { 26 for(register int i=1; i<=n; i++) 27 { 28 s[i]=i;//i的父节点为s[i] 29 } 30 sort(edge+1,edge+m+1,cmp); 31 for(register int i=1; i<=m; i++) 32 { 33 int x=Find(edge[i].u); 34 int y=Find(edge[i].v); 35 if(x==y) 36 continue; 37 ans+=edge[i].w; 38 cnt++; 39 s[y]=x; 40 if(cnt==n-1) 41 break; 42 } 43 return ans; 44 } 45 46 int main() 47 { 48 ios::sync_with_stdio(false); 49 cin>>n>>m; 50 for(register int i=1; i<=m; i++) 51 cin>>edge[i].u>>edge[i].v>>edge[i].w; 52 Kruskal(); 53 if(cnt>=n-1) 54 cout<<ans<<endl; 55 else 56 cout<<"orz"<<endl; 57 return 0; 58 }
呜呜呜痛并快乐着