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