最小生成树

《不能使用最小生成树的情况》

在没有说不能产生回路时:

 

 这个情况是不能使用最小生成树算法的,因为边可以是负的,如果再加上一条边,这条边是负的,正好还可以减少权重

《Kruskal算法的大用》

用kruskal算法就像用一个进度条求最小生成树一样,即如果在开始时,最小生成树已经完成了一部分,可以用

kruskal算法继续完成,或者有些必要条件必须选边用这个算法更加方便,如:

 

 

 

 

 连接格点这道题,如果像平常使用kruskal算法包含sort会超时,

那我们可以在建图的时候就按照边权小的先建

 1 #include<bits/stdc++.h>
 2 #define N 1010
 3 using namespace std;
 4 int n,m;
 5 int fa[N*N],tot;
 6 int find(int x)//并查集基本操作
 7 {
 8     if(fa[x]==x)
 9         return x;
10     return fa[x]=find(fa[x]);
11 }
12 inline int merge(int x,int y)//并查集基本操作
13 {
14     int fa_x=find(x);
15     int fa_y=find(y);
16     if(fa_x!=fa_y){
17         fa[fa_y]=fa_x;
18         return 1;//已经连了一条边
19     }
20     return 0;
21 }
22 int main()
23 {
24     scanf("%d %d",&n,&m);
25     for(int i=1;i<=n*m;i++)//并查集初始化
26        fa[i]=i;
27     int x1,y1,x2,y2;
28     while(~scanf("%d %d %d %d",&x1,&y1,&x2,&y2)){
29         int u=(x1-1)*m+y1,v=(x2-1)*m+y2;//转换为对应的编号
30         merge(u,v);//合并
31     }
32     for(int i=1;i<=m;i++)//竖向合并一遍
33         for(int j=1;j<n;j++){
34             int u=(j-1)*m+i,v=j*m+i;//坐标转换
35             if(merge(u,v))//当前两点有一条边连接
36                 tot++;//竖向答案+1
37         }
38     for(int i=1;i<=n;i++)//横向合并一遍
39         for(int j=1;j<m;j++){
40             int u=(i-1)*m+j,v=(i-1)*m+j+1;//坐标转换
41             if(merge(u,v))//当前两点有一条边连接
42                 tot+=2;//横向答案+2
43         }
44     printf("%d\n",tot);
45     return 0;
46 }

 《kruskal算法与连通块之间的关系》

因为并查集本身在建树的过程就是连通块不断减少的过程,最终使只剩下一个连通块

 

 

 

 

 我的错误思想:先做一遍最短路,然后将最短路中的边排序,将最大边相连的点用卫星连起来

,但是这个思想是错误的,因为我没有考虑到一旦有点用卫星连起来,这些点之间权重就全部变成了0

之前作的最短路就明显不对了

正确思路:

模型抽象:选择最小的d,去除点之间大于d的路径,然后能够使连通块的数量<=k

很明显,d与连通块的数量之间有这样的关系:

 

 

 按照正常的kruskal算法,当我们使用sides[]的w时,其实就是<=w的边全部已经建好,>w的边还没有用的建立连通块的情况

我们只要找到一个sides[]的w,使;连通块的数第一次<=k

 1 #include <cstring>
 2 #include <iostream>
 3 #include <algorithm>
 4 #include <cmath>
 5 
 6 #define x first
 7 #define y second
 8 
 9 using namespace std;
10 
11 typedef pair<int, int> PII;
12 
13 const int N = 510, M = N * N / 2;
14 
15 int n, k, m;
16 struct Edge
17 {
18     int a, b;
19     double w;
20     bool operator< (const Edge &t) const
21     {
22         return w < t.w;
23     }
24 }e[M];
25 PII q[M];
26 int p[N];
27 
28 double get_dist(PII a, PII b)
29 {
30     int dx = a.x - b.x;
31     int dy = a.y - b.y;
32     return sqrt(dx * dx + dy * dy);
33 }
34 
35 int find(int x)
36 {
37     if (p[x] != x) p[x] = find(p[x]);
38     return p[x];
39 }
40 
41 int main()
42 {
43     cin >> n >> k;
44     for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
45     for (int i = 0; i < n; i ++ )
46         for (int j = 0; j < i; j ++ )
47             e[m ++ ] = {i, j, get_dist(q[i], q[j])};
48 
49     sort(e, e + m);
50     for (int i = 0; i < n; i ++ ) p[i] = i;
51 
52     int cnt = n;
53     double res = 0;
54     for (int i = 0; i < m; i ++ )
55     {
56         if (cnt <= k) break;
57 
58         int a = find(e[i].a), b = find(e[i].b);
59         double w = e[i].w;
60         if (a != b)
61         {
62             p[a] = b;
63             cnt -- ;
64             res = w;
65         }
66     }
67 
68     printf("%.2lf\n", res);
69 
70     return 0;
71 }
72 
73 作者:yxc
74 链接:https://www.acwing.com/activity/content/code/content/151271/
75 来源:AcWing
76 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Code
 1 #include <cstring>
 2 #include <iostream>
 3 #include <algorithm>
 4 #include <cmath>
 5 
 6 #define x first
 7 #define y second
 8 
 9 using namespace std;
10 
11 typedef pair<int, int> PII;
12 
13 const int N = 510, M = N * N / 2;
14 
15 int n, k, m;
16 struct Edge
17 {
18     int a, b;
19     double w;
20     bool operator< (const Edge &t) const
21     {
22         return w < t.w;
23     }
24 }e[M];
25 PII q[M];
26 int p[N];
27 
28 double get_dist(PII a, PII b)
29 {
30     int dx = a.x - b.x;
31     int dy = a.y - b.y;
32     return sqrt(dx * dx + dy * dy);
33 }
34 
35 int find(int x)
36 {
37     if (p[x] != x) p[x] = find(p[x]);
38     return p[x];
39 }
40 
41 int main()
42 {
43     cin >> n >> k;
44     for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
45     for (int i = 0; i < n; i ++ )
46         for (int j = 0; j < i; j ++ )
47             e[m ++ ] = {i, j, get_dist(q[i], q[j])};
48 
49     sort(e, e + m);
50     for (int i = 0; i < n; i ++ ) p[i] = i;
51 
52     int cnt = n;
53     double res = 0;
54     for (int i = 0; i < m; i ++ )
55     {
56         if (cnt <= k) break;
57 
58         int a = find(e[i].a), b = find(e[i].b);
59         double w = e[i].w;
60         if (a != b)
61         {
62             p[a] = b;
63             cnt -- ;
64             res = w;
65         }
66     }
67 
68     printf("%.2lf\n", res);
69 
70     return 0;
71 }
72 
73 作者:yxc
74 链接:https://www.acwing.com/activity/content/code/content/151271/
75 来源:AcWing
76 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 《解决最小生成树的一般思考方式》

我们一般思考最小生成树都最好将其看做连通块的合并来看,

因为一个连通块中的关系我们是不用考虑的(因为这里面的最小生成树已经生成)

然后最小生成树就成了多个连通块之间连上一条边的问题,极大地减少了复杂性

 

 

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 using namespace std;
 5 const int N = 6001;
 6 int sizes[N], fa[N];
 7 struct Node
 8 {
 9     int a, b, w;
10 } sides[N];
11 int find(int x)
12 {
13     if (fa[x] != x)
14         fa[x] = find(fa[x]);
15     return fa[x];
16 }
17 int main()
18 {
19     int t;
20     cin >> t;
21     while (t--)
22     {
23         int n;
24         cin >> n;
25         for (int i = 1; i <= n - 1; i++)
26         {
27             int a, b, w;
28             cin >> a >> b >> w;
29             sides[i] = {a, b, w};
30         }
31         sort(sides + 1, sides + n, [](struct Node a, struct Node b)
32              { return a.w < b.w; });
33         for (int i = 1; i <= n; i++)
34         {
35             fa[i] = i;
36             sizes[i] = 1;
37         }
38         int res = 0;
39         for (int i = 1; i <= n - 1; i++)
40         {
41             int u = sides[i].a, v = sides[i].b, w = sides[i].w;
42             int fu = find(u), fv = find(v);
43             if (fu != fv)
44             {
45                 res += (sizes[fu] * sizes[fv] - 1) * (w + 1);
46                 fa[fv] = fu;
47                 sizes[fu] += sizes[fv];
48             }
49         }
50         cout << res << endl;
51     }
52     return 0;
53 }

 《次小生成树》

 

 更详细博客:https://www.acwing.com/solution/content/8300/

关于如何求任意两点之间最小生成树的最大边权的dfs有点讲究

 

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <vector>
 5 using namespace std;
 6 const int N = 501, M = 1e4 + 1;
 7 vector<int> sides[N];
 8 vector<int> sidesW[N];
 9 int fa[N], n, m;
10 struct Node
11 {
12     int u, v, w;
13     bool isTree;
14 } nodes[M];
15 int find(int x)
16 {
17     if (fa[x] != x)
18         fa[x] = find(fa[x]);
19     return fa[x];
20 }
21 int dist1[N][N], dist2[N][N];
//x为当前的点,f是x的父节点,maxN是已求出的最大边权,ermaxN是已求出的第二最大边权
//d1是dist1[i][],表示i点到x点的最大边权,d2是dist2[i][]表示i点到x点的第二最大边权
22 void dfs(int x, int f, int maxN, int ermaxN, int d1[], int d2[]) 23 { 24 d1[x] = maxN; 25 d2[x] = ermaxN; 26 for (int i = 0; i < sides[x].size(); i++) 27 { 28 int child = sides[x][i], w = sidesW[x][i]; 29 //十分注意这里直接改变maxN与ermaxN是一个十分愚蠢的做法 30 //因为到时候我还要回溯,如果改变了而没有还原 31 //则会出现十分严重的问题 32 if (child != f) 33 { 34 int m1 = maxN, m2 = ermaxN; 35 if (w > m1) 36 { 37 m2 = m1; 38 m1 = w; 39 } 40 else if (w < m1 && w > m2) 41 m2 = w; 42 dfs(child, x, m1, m2, d1, d2); 43 } 44 } 45 } 46 long long sum = 0; 47 void Kruskal() 48 { 49 for (int i = 1; i <= n; i++) 50 fa[i] = i; 51 sort(nodes + 1, nodes + 1 + m, [](struct Node a, struct Node b) 52 { return a.w < b.w; }); 53 for (int i = 1; i <= m; i++) 54 { 55 int u = nodes[i].u, v = nodes[i].v, w = nodes[i].w; 56 int fu = find(u), fv = find(v); 57 if (fu != fv) 58 { 59 fa[fv] = fu; 60 sum += w; 61 /* cout << u << " " << v << endl; */ 62 sides[u].push_back(v), sides[v].push_back(u); 63 sidesW[u].push_back(w), sidesW[v].push_back(w); 64 nodes[i].isTree = true; 65 } 66 } 67 } 68 int main() 69 { 70 scanf("%d%d", &n, &m); 71 for (int i = 1; i <= m; i++) 72 { 73 int a, b, w; 74 scanf("%d%d%d", &a, &b, &w); 75 nodes[i] = {a, b, w, false}; 76 } 77 //生成最小生成树 78 Kruskal(); 79 //找到任意两个点之间的最大边权数 80 for (int i = 1; i <= n; i++) 81 dfs(i, -1, 0, 0, dist1[i], dist2[i]); 82 /* cout << "success" << endl; */ 83 //枚举每一条非树边,找到一条非树边的>这个边在最小生成树中两个点之间的最大边权数 84 //然后取这个中最小的 85 long long res = 1e18; 86 for (int i = 1; i <= m; i++) 87 { 88 int u = nodes[i].u, v = nodes[i].v, w = nodes[i].w; 89 if (!nodes[i].isTree) 90 { 91 if (w > dist1[u][v]) 92 res = min((sum - dist1[u][v] + w), res); 93 else if (w == dist1[u][v] && w > dist2[u][v]) 94 res = min((sum - dist2[u][v] + w), res); 95 } 96 } 97 cout << res; 98 return 0; 99 }

 

posted @ 2022-09-04 11:13  次林梦叶  阅读(29)  评论(0)    收藏  举报