最小度限制生成树就是给一个图,让求它的最小生成树。找的的最小生成树满足并且点vo的度最大为k。
算法流程如下:
1.将该点(以下用v0表示)从图中删除,将得到p个连通分量。
2.对每个连通分量求最小生成树。
3.从每个连通分量中找与v0关联的权值最小的边,与v0相连接,这样将得到v0的最小p度生成树
4.如果 k < p 那么这种树是不存在的。
5.如果 k >= p ,那么考虑构建 p+1度最小生成树,即加入每一条与v0相连的且不在当前的树中的边。
6.显然在第5步将其加入树中 ,必然会存在一个环,那么删掉该环中与v0不关联的权值最大边,将得到加入该边后的最小生成树,且v0是p+1度的。
7.枚举上述 5,6 的边找树权值最小,那么即是p+1度限制的最小生成树。如果p+1度最小生成树的值大于p度最小生成树的话直接输出当前p度的值即可。
8.重复5.6.7,直到k 度最小生成树出现。
显然上述算法的1-->3步可以用prim算法实现。复杂度是O(n^2)。而5--->7如果枚举每条边进行增删操作则是O(n^2)的复杂度,总的复杂度达到了O(k*n^2)。在求次小生成树的过程中我们用到了一个path[x][y]记录x -->y的最长边。这里也是用类似的方法设立一个del[]数组,记录v0--->vi路径上的最长边。这样我们每次只要枚举max(del[i] - g[v0][i])即可。注意每次操作后都要更新该连通块的del[]。这样复杂度就降为O(k*n)。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #include<stack> 8 #include<string> 9 #include<vector> 10 #include<cstdlib> 11 #include<map> 12 #include<set> 13 using namespace std; 14 #define inf 0x3f3f3f3f 15 const int maxn = 25; 16 int m,n,k,g[maxn][maxn],dis[maxn],pre[maxn],ans; 17 bool vis[maxn],link[maxn][maxn];//link[][]数组来保存在MST中的边 18 struct node 19 { 20 int u,v,len; 21 node(){} 22 node(int u, int v, int len):u(u),v(v),len(len){} 23 }del[maxn];//保存 0到当前节点的路径上的最长边 24 int kk; 25 void prim(int st)//求包含st的连通块的MST 26 { 27 vis[st] = 1; 28 memset(pre, -1, sizeof(pre)); 29 for (int i = 1; i < n; ++i) 30 { 31 dis[i] = g[st][i]; 32 pre[i] = st; 33 } 34 while(1) 35 { 36 int tmp = inf, nt = st; 37 for (int i = 1; i < n; ++i) 38 { 39 if (!vis[i] && dis[i] < tmp) 40 { 41 nt = i; 42 tmp = dis[i]; 43 } 44 } 45 if (st == nt) break;//该连通分量最小生成树完成 46 if (g[0][nt] < g[0][kk]) kk = nt;//kk保存该连通分量到0距离最近的点 47 link[pre[nt]][nt] = link[nt][pre[nt]] = 1;//将边加入到MST中 48 vis[nt] = 1; 49 ans += dis[nt]; 50 for (int i = 1; i < n; ++i) 51 { 52 if (!vis[i] && dis[i] > g[nt][i]) 53 { 54 pre[i] = nt;//记录该点前驱 55 dis[i] = g[nt][i]; 56 } 57 } 58 } 59 } 60 void dfs(int cur, int cpre, int u, int v)//修改当前连通分量中到达0的路径上的最大边 61 { 62 //cur 当前节点,cpre为当前节点的前驱,(u,v)表示当前节点到0节点的路径上最大边 63 for (int i = 1; i < n; ++i) 64 { 65 if (cpre != i && link[cur][i]) 66 { 67 if (cpre == -1|| g[cur][i] >= g[u][v])//当前边大于之前保存的最大边 68 { 69 del[i] = node(cur, i, g[cur][i]); 70 dfs(i, cur, cur, i); 71 } 72 else 73 { 74 del[i] = node(u, v, g[u][v]); 75 dfs(i, cur, u, v); 76 } 77 } 78 } 79 } 80 void solve() 81 { 82 for (int i = 1; i < n; ++i) 83 { 84 if (vis[i]) continue; 85 k--; 86 kk = i; 87 prim(i); 88 ans += g[0][kk]; 89 link[kk][0] = link[0][kk] = 1; 90 dfs(kk, -1, -1, -1); 91 } 92 while(k--) 93 { 94 int c = 0, nt = 0; 95 for (int j = 1; j < n; ++j)//枚举所有节点,找出最大边 96 { 97 if (link[0][j] || g[0][j] == inf) continue; 98 if(c < del[j].len - g[0][j])//找出最大的添删操作 99 { 100 nt = j; 101 c = del[j].len - g[0][j]; 102 } 103 } 104 if (c == 0) break; 105 ans -= c; 106 link[del[nt].u][del[nt].v] = link[del[nt].v][del[nt].u] = false; 107 link[0][nt] = link[nt][0] = 1; 108 dfs(nt, 0, -1, -1);//每次操作完成后修改当前连通分量的最长边 109 } 110 printf("Total miles driven: %d\n",ans); 111 } 112 void init() 113 { 114 char s1[20],s2[20]; 115 int w, u, v; 116 n = 0; 117 map <string,int> name; 118 map <string,int>::iterator it1,it2; 119 name.clear(); 120 name["Park"] = n++; 121 memset(g, 0x3f, sizeof(g)); 122 memset(vis, 0, sizeof(vis)); 123 memset(link, 0, sizeof(link)); 124 for (int i = 0; i < m; ++i) 125 { 126 scanf("%s %s %d", &s1, &s2, &w); 127 it1 = name.find(s1); 128 it2 = name.find(s2); 129 if (it1 != name.end()) u = it1->second; 130 else 131 { 132 name[s1] = n; 133 u = n++; 134 } 135 if (it2 != name.end()) v = it2->second; 136 else 137 { 138 name[s2] = n; 139 v = n++; 140 } 141 if (g[u][v] > w) 142 g[u][v] = g[v][u] = w; 143 } 144 scanf("%d", &k); 145 ans = 0; 146 } 147 int main() 148 { 149 while(scanf("%d", &m) != EOF) 150 { 151 init(); 152 solve(); 153 } 154 return 0; 155 }