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 }

呜呜呜痛并快乐着

posted @ 2022-04-13 21:38  爱吃虾滑  阅读(213)  评论(0)    收藏  举报