次小生成树

首先求出最小生成树,我们枚举每条不在最小生成树上的边,并把这条边放到最小生成树上面,然后就一定会形成环,那么我们在这条环路中取出一条最长的路(除了新加入的那一条边),最终我们得到的权值就是次小生成树的权值。

prim实现

我们在求解次小生成树的时候我们要使用一个二维数组$maxd[i][j]$表示最小生成树中$i$点到$j$点的最远距离,我们使用动态规划的思想来计算这个数组,比如当前节点为$x$,他的父亲节点为$per[x]$,以及根节点$root$,那么

$$maxd[root][x] = max(maxd[root][per[x]] , maxd[per[x]][x])$$

我们就会得到最终的结果数组
我们还需要数组:$connect[i][j]$表示最小生成树中这条边有没有被用到,剩下的就是我们要去模拟算法解释里所说的删边以及添边的操作了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define INF 0x3f3f3f3f

using namespace std;
const int MAXN = 110;
int n,m,mapp[MAXN][MAXN];
bool vis[MAXN],connect[MAXN][MAXN];
int dis[MAXN],maxd[MAXN][MAXN],per[MAXN];
void Init()
{
memset(mapp,INF,sizeof(mapp));
memset(connect,false,sizeof(connect));
}
int prim()
{
memset(maxd,0,sizeof(maxd));
memset(vis,false,sizeof(vis));
for(int i = 1;i <= n;i ++)
{
dis[i] = mapp[1][i];per[i] = 1;//首先父亲节点都是根节点
}
vis[1] = 1;
dis[1] = 0;
int res = 0;
for(int i = 1;i < n;i ++)
{
int index = -1,temp = INF;
for(int j = 1;j <= n;j ++)
if(!vis[j] && dis[j] < temp)
{
index = j;temp = dis[j];
}
if(index == -1) return res;
vis[index] = 1;

//这条边已经在最小生成树中,后面我们就不能添加这条边了
connect[index][per[index]] = false;connect[per[index]][index] = false;
res += temp;

//更新点之间的最大值
maxd[per[index]][index] =maxd[index][per[index]] = temp;
for(int j = 1;j <= n;j ++)
{
if(j != index && vis[j])//只是更新我们已经遍历过来的节点
{
maxd[index][j]=max(maxd[j][per[index]],dis[index]);
maxd[j][index]=maxd[index][j];
}
if(!vis[j] && mapp[index][j] < dis[j])
{
dis[j] = mapp[index][j];
per[j] = index;
}
}
}
return res;
}
int main()
{
int T;
scanf("%d\n",&T);
while(T--)
{
scanf("%d%d",&n,&m);
Init();
int u,v,w;
for(int i = 0;i < m;i ++)
{
scanf("%d%d%d",&u,&v,&w);
mapp[u][v] = w;mapp[v][u] = w;
connect[u][v] = true;connect[v][u] = true;
}
int ans = prim();
bool over = false;
//如果有某条边没有被最小生成树使用,
//并且i~j的最大值大于图中得到最大值,那么就表示次小生成树存在
//相反就不会存在
for(int i = 1;!over && i <= n;i ++)
for(int j = 1;j <= n;j ++)
{
if(connect[i][j] == false || mapp[i][j] == INF)
continue;
if(mapp[i][j] == maxd[i][j])//当边长度相同是就是表示最小生成树相同
{
over = 1;
break;
}
}
if(over)
printf("Not Unique!\n");
else
printf("%d\n",ans);
}
//如果我们需要求解次小生成树的权值时,
//我们就要把在最小生成树中没有用过的边,
//加上然后减去对应环中最大的路径
return 0;
}

Kruskal实现

kruskla算法中我们枚举的边权值会依次增大,那么就会给我们计算提供一定的便利,但是因为kruskal的实现方式和prim有所不同,所以kruskal需要存储当前最小生成树中的节点,然后我们再去更新$maxd$数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <vector>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;
int n,m;
struct data
{
int u,v,w;
bool vis;
} p[20010];
vector<int>G[110];
int per[110],maxd[110][110];
bool cmp(data a,data b)
{
return a.w < b.w;
}
int Union_Find(int x)
{
return x == per[x] ? x: per[x] = Union_Find(per[x]);
}
void kruskal()
{
sort(p,p+m,cmp);
for(int i=0; i<=n; i++)//初始化
{
G[i].clear();
G[i].push_back(i);
per[i]=i;
}
int sum=0,k=0;//sum是最小生成树的值
for(int i=0; i<m; i++)
{
if(k==n-1) break;
int x1=Union_Find(p[i].u),x2=Union_Find(p[i].v);
if(x1!=x2)
{
k++;
p[i].vis=1;//这条边已经用过了
sum+=p[i].w;
int len_x1=G[x1].size();
int len_x2=G[x2].size();
for(int j=0; j<len_x1; j++)//更新两点之间距离的最大值
for(int k=0; k<len_x2; k++)
//因为后面的边会越来越大,所以这里可以直接等于当前边的长度
maxd[G[x1][j]][G[x2][k]]=maxd[G[x2][k]][G[x1][j]]=p[i].w;
per[x1]=x2;
//因为per[x1] = x2,在Union_Find函数中要寻找和x1相关联节点的跟节点的时候,
//都会找到x2,所以这里不用再去更新和x1节点相连的节点
for(int j=0; j<len_x1; j++)
G[x2].push_back(G[x1][j]);

}
}
int cisum=INF;//次小生成树的权值
for(int i=0; i<m; i++)
if(!p[i].vis)
cisum=min(cisum,sum+p[i].w-maxd[p[i].u][p[i].v]);
if(cisum>sum)
printf("%d\n",sum);
else
printf("Not Unique!\n");
}
int main()
{
int T;
scanf("%d\n",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=0; i<m; i++)
{
scanf("%d%d%d",&p[i].u,&p[i].v,&p[i].w);
p[i].vis = false;
}
kruskal();
}
return 0;
}