/*
这题调得快吐血了,做得真辛苦
这题主要就是求最小限度生成树,即给定一棵树,在某个节点上加上这个节点的最大度数,然后求一棵满足这样条件的
树且最终的权值和最小。这道题就是在根节点Park上做度数限定,然后求最小限度生成树
最小限度生成树的求法与MST(最小生成树密不可分)主要思路有以下几个步骤
1)将根节点从图中去除掉
2)对去除根节点的图求MST,注意这里去除根后的图可能是不连通的,所以计算MST的时候要对每个连通图都进行计算
这里我选择用Kruscal算法+并查集求MST.求完后对每个节点按照其所属的连通分量进行着色,并统计连通分量的个数
3)针对每一个连通分量,选择从根节点到这个连通分量里的节点的具有最小权值的边加入图中,假设有conn个连通分
量则一共需要加conn条边
4)由1)2)3)步骤即得到一棵conn最小限度生成树
5)假设题目限定的停车位数是kdeg,那么还需要找kdeg - conn条从根节点出发的边,所以循环kdeg - conn次,每次做
如下操作:
a)假设对于节点k, 边[root, k, w]不在当前树中(权值为w), 那么如果把这条边加入树中就会构成一条环
b)假设这个环中不是与根直接相连的具有最大权值的边为[from, to, weight]
遍历所有的树中的节点,寻找具有最大weight - w值的边,如果这个差值大于0,则将这条边[root, k]加入树种
并去除边[from, to],如果差值小于等于0则退出
感觉这道题比较折腾啊,主要是代码写得太多太容易出错
其中两个出错的地方是
1)计算MST时算错了,把不同连通分量的color值的计算弄混淆了
2)一开始edges数组的下标设成了25,结果Runtime Error,后来恍然大悟,20个节点的最大边数是c(20, 2)远远不止25,
看来还是得细心点
6099743 bobten2008 1639 Accepted 252K 16MS C++ 5170B 2009-11-07 19:12:34
Over!
*/
#include <iostream> #include <algorithm> #include <string> #define MAX_N 25 using namespace std; //记录所有节点名 struct node { string name; int con[MAX_N + 1]; //记录原始的当前点与其他点间边的权值 }nodes[MAX_N + 1]; int nodesn; //结点数量 int kdeg; //最小限度数 struct edge //用来记录所有边 { int from, to, w; edge() { from = to = w = 0; } }edges[MAX_N * MAX_N + 1]; int edgen; int graph[MAX_N + 1][MAX_N + 1]; int color[MAX_N + 1]; int bfsq[MAX_N + 1][4], head, tail; bool v[MAX_N + 1]; void init() { memset(nodes, 0, sizeof(nodes)); memset(graph, 0, sizeof(graph)); nodesn = 0; edgen = 0; } int getIndex(const string &str) { for(int i = 0; i < nodesn; i++) if(nodes[i].name == str) return i; return -1; } //插入边[str1, str2, w] void processNodes(const string &str1, const string &str2, int w) { int index1 = getIndex(str1); if(index1 == -1) { index1 = nodesn++; nodes[index1].name = str1; } int index2 = getIndex(str2); if(index2 == -1) { index2 = nodesn++; nodes[index2].name = str2; } nodes[index1].con[index2] = w; nodes[index2].con[index1] = w; if(str1 != "Park" && str2 != "Park") { edges[edgen].from = index1; edges[edgen].to = index2; edges[edgen].w = w; edgen++; } } bool compare(const edge &e1, const edge &e2) { return e1.w <= e2.w; } //并查集相关 int sets[MAX_N + 1]; int find(int id) { if(sets[id] == id) return id; else return sets[id] = find(sets[id]); } void joint(int id1, int id2) { int sid1 = find(id1), sid2 = find(id2); if(sid1 == sid2) return; else sets[sid1] = sid2; } //Kruskal 算法, //返回联通分量的个数 int solveMST(int root) { int e = 0, t, colorseq = 0; for(e = 0; e < nodesn; e++) { color[e] = 0; sets[e] = e; } for(e = 0; e < edgen; e++) { int from = edges[e].from; int to = edges[e].to; int w = edges[e].w; if(find(from) == find(to)) continue; else { joint(from, to); graph[from][to] = graph[to][from] = w; } } //着色 for(e = 0; e < nodesn; e++) { if(color[e] || e == root) continue; int sid = find(e); color[e] = ++colorseq; for(t = e + 1; t < nodesn; t++) { if(t == root) continue; if(!color[t] && find(t) == sid) color[t] = color[e]; } } return colorseq; } //统计记录从root 到 当前tree中所有点路径上除去和root直连的边中的权值最大的边的 from to weight void getMaxEdgeInPath(int root, int maxarray[][3]) { head = tail = 1; memset(bfsq, 0, sizeof(bfsq)); memset(v, 0, sizeof(v)); bfsq[tail][0] = root; bfsq[tail][3] = 0; tail = tail % MAX_N + 1; v[root] = true; while(head != tail) { int curId = bfsq[head][0]; int from = bfsq[head][1]; int to = bfsq[head][2]; int curMax = bfsq[head][3]; head = head % MAX_N + 1; if(curId != root && from != root) { maxarray[curId][0] = from; maxarray[curId][1] = to; maxarray[curId][2] = curMax; } for(int toid = 0; toid < nodesn; toid++) { int curW; if(v[toid] || (curW = graph[curId][toid]) == 0) continue; v[toid] = true; int fromm = from, too = to, curMaxx = curMax; if(curW > curMax && curId != root) { fromm = curId; too = toid; curMaxx = curW; } bfsq[tail][0] = toid; bfsq[tail][1] = fromm; bfsq[tail][2] = too; bfsq[tail][3] = curMaxx; tail = tail % MAX_N + 1; } } } void solveKDegreeContraintTree() { //记录从root 到 当前tree中所有点路径上除去和root直连的边中的权值最大的边的 from to weight int maxEdgeInPath[MAX_N + 1][3]; //记录点是否和root直连 bool conDirectToRoot[MAX_N + 1]; //记录root和每一个连通分量之间权值最小的边及权值 int minVal[MAX_N + 1][2]; memset(maxEdgeInPath, 0, sizeof(maxEdgeInPath)); memset(conDirectToRoot, 0, sizeof(conDirectToRoot)); memset(minVal, 0, sizeof(minVal)); int root = getIndex("Park"); int conn = solveMST(root); int i, c; //选择root和每一个连通分量之间权值最小的边及权值 for(i = 0; i < nodesn; i++) { int w; if(i == root || (w = nodes[root].con[i]) == 0) continue; int c = color[i]; if(minVal[c][1] == 0 || w < minVal[c][1]) { minVal[c][0] = i; minVal[c][1] = w; } } //链接root与conn个连通分量间具有最小权值的边 for(c = 1; c <= conn; c++) { int to = minVal[c][0]; int w = minVal[c][1]; conDirectToRoot[to] = true; //标记直连 graph[root][to] = graph[to][root] = w; //连边 } //添加额外的kdeg - conn条边 int time = kdeg - conn; while(time--) { memset(maxEdgeInPath, 0, sizeof(maxEdgeInPath)); getMaxEdgeInPath(root, maxEdgeInPath); int minVal, minNode, maxsubval = 0; //遍历所有节点,寻找具有最大权值差的点minNode for(int k = 0; k < nodesn; k++) { int w; if((w = nodes[root].con[k]) == 0 || conDirectToRoot[k]) continue; if((maxEdgeInPath[k][2] - w) > maxsubval) { maxsubval = maxEdgeInPath[k][2] - w; minVal = w; minNode = k; } } //无法再优化,则退出 if(maxsubval == 0) break; //更换边 int f = maxEdgeInPath[minNode][0], t = maxEdgeInPath[minNode][1]; graph[f][t] = graph[t][f] = 0; graph[root][minNode] = graph[minNode][root] = minVal; } } int main() { int in, i, j, w; string from, to; init(); cin>>in; for(i = 0; i < in; i++) { cin>>from>>to>>w; processNodes(from, to, w); } cin>>kdeg; sort(edges, edges + edgen, compare); solveKDegreeContraintTree(); int res = 0; for(i = 0; i < nodesn; i++) for(j = i + 1; j < nodesn; j++) res += graph[i][j]; printf("Total miles driven: %d/n", res); return 0; }