UVa Problem 10160 Servicing Stations (服务站)

// Servicing Stations (服务站) // PC/UVa IDs: 110804/10160, Popularity: B, Success rate: low Level: 3 // Verdict: Accepted // Submission Date: 2011-08-10 // UVa Run Time: 0.056s // 版权所有(C)2011,邱秋。metaphysis # yeah dot net // // 该题目的实质是求图的最小支配集顶点个数。求图的最小支配集问题是 NP 完全问题,目前无高效的算法。 // 既然要枚举集合的所有子集,那就枚举吧,但是等一等,枚举需要个顺序,先枚举元素数量小的子集,要不 // 然,先枚举元素个数大的子集可能会浪费时间,毕竟从元素个数小的子集枚举到元素个数大的子集可以保证 // 找到最小支配集而不再浪费更多的时间,哎呀,时间是金钱,不是吗?可能什么时候某个强人发现 NP 难问 // 题的 P 时间算法,或者证明 P = NP 那就好了,希望在有生之年能够看到!! // // 为了提高效率,压缩程序运行时间,采用了以下优化方法: // (1)若图可以拆分为多个不相连的子图,则先予拆分,然后对子图求最小支配集的顶点个数相加(之前由 // 于未拆分图,导致了多次 TLE)。 // (2)对于求两个集合的并采用了位操作,事先将某个顶点的邻接表表示为一个整数以便用与操作来代替集合 // 的并。 // (3)枚举时,先考虑度数大的顶点。 // // 枚举方法参考了 [J. Loughry, J.I. van Hemert, L. Schoofs, Efficiently Enumerating // the Subsets of a Set, 2000] #include <iostream> #include <vector> #include <algorithm> #include <set> using namespace std; #define MAX_TOWN 35 typedef long long unsigned LLUINT; bool finish; int minimum; vector < LLUINT > edge; LLUINT target_tag, origin_tag; // 检查顶点集合是否为支配集。 void check(int flag[], int position) { int index = 0; LLUINT new_tag = origin_tag, old_tag = origin_tag; for (int i = 0; i < edge.size(); i++) { if ((index < position) && (flag[index] == i)) { new_tag |= edge[i]; if (new_tag > old_tag) old_tag = new_tag; else return; index++; } } if (new_tag == target_tag) { minimum = index; finish = true; } } // 按 Banker’s Sequence 枚举图的子集。 void generate(int flag[], int position, int positions) { if (finish) return; if (position < positions) { if (position == 0) { for (int i = 0; i < edge.size(); i++) { flag[position] = i; generate(flag, position + 1, positions); } } else { for (int i = flag[position - 1] + 1; i < edge.size(); i++) { flag[position] = i; generate(flag, position + 1, positions); } } } else check(flag, position); } // 枚举图向量的子集以判断是否是一个支配集。 void enumerating_subset() { for (int i = 1; i <= edge.size(); i++) { int * flag = new int[edge.size()]; generate(flag, 0, i); delete [] flag; if(finish) return; } } bool cmp(LLUINT x, LLUINT y) { return x > y; } // 获取图的最小支配集顶点数(MDSN)。 int mdsn(vector < vector < int > > &vertex) { int base = 0; origin_tag = 0; target_tag = 0; vector < bool > dirty(vertex.size()); fill(dirty.begin(), dirty.end(), false); // 清掉度为 0 的点。度为 0 的点和其他点都无通路,则该图的最小支配集必须要包括 // 该顶点。则表示最小支配集顶点个数的变量 base 需增加 1。 for (int i = 0; i < vertex.size(); i++) { if (vertex[i].size() == 0) { base++; origin_tag |= ((LLUINT)1 << i); } if (vertex[i].size() == 1 && dirty[i] == false) { dirty[i] = true; if (dirty[vertex[i][0] - 1] == false) { base++; dirty[vertex[i][0] - 1] = true; } } target_tag |= ((LLUINT)1 << i); } // 清掉度为 1 的点。度为 1 的点表明该顶点 A 只与其他一个顶点 B 相连接,则可 // 将 B 计入最小支配集中。 edge.clear(); for (int i = 0; i < vertex.size(); i++) { if (dirty[i] == true) { origin_tag |= ((LLUINT)1 << i); for (int j = 0; j < vertex[i].size(); j++) origin_tag |= ((LLUINT)1 << (vertex[i][j] - 1)); } if (dirty[i] == false && vertex[i].size() > 0) { LLUINT t = ((LLUINT)1 << i); for (int j = 0; j < vertex[i].size(); j++) t |= ((LLUINT)1 << (vertex[i][j] - 1)); edge.push_back(t); } } // 排序,度数大的点首先考虑。 sort(edge.begin(), edge.end(), cmp); minimum = 0; finish = false; enumerating_subset(); return (base + minimum); } int servicing_stations(vector < vector < int > > &vertex) { // 使用宽度优先搜索分离子图,计算子图的最小支配集顶点个数,相 // 加即为原图的最小支配集顶点个数。 int total = 0; while (vertex.size() > 0) { vector < vector < int > > open; set < int > close; int size = 0; open.push_back(vertex[0]); close.insert(vertex[0][0]); vertex.erase(vertex.begin()); while (open.size() > size) { int origin = size; int current = open.size() - 1; size = open.size(); for (int i = origin; i <= current; i++) { for (int j = 1; j < open[i].size(); j++) { if (close.find(open[i][j]) == close.end()) { close.insert(open[i][j]); for (int m = 0; m < vertex.size(); m++) { if (vertex[m][0] == open[i][j]) { open.push_back(vertex[m]); vertex.erase(vertex.begin() + m); break; } } } } } } // 调整分离出的子图的序号以便后续操作。 vector < vector < int > > tmp; for (int c = 1; c <= MAX_TOWN; c++) if (close.find(c) != close.end()) { for (int i = 0; i < open.size(); i++) if (open[i][0] == c) tmp.push_back(open[i]); } for (int i = 0; i < tmp.size(); i++) { int current = tmp[i][0]; for (int m = 0; m < tmp.size(); m++) for (int n = 1; n < tmp[m].size(); n++) if (tmp[m][n] == current) tmp[m][n] = (i + 1); } for (int i = 0; i < tmp.size(); i++) tmp[i].erase(tmp[i].begin()); // 计算该子图的最小支配集顶点数。 total += mdsn(tmp); } return total; } int main(int ac, char *av[]) { int n; int m; int x, y; vector < vector < int > > vertex; while (cin >> n >> m, n && m) { vertex.clear(); vertex.resize(n); // 数组的第一个数存放顶点的序号。 for (int i = 0; i < n; i++) vertex[i].push_back((i + 1)); // 读入镇之间的通路。 for (int i = 0; i < m; i++) { // 自身连接到自身的通路不添加。 cin >> x >> y; if (x != y) { vertex[x - 1].push_back(y); vertex[y - 1].push_back(x); } } cout << servicing_stations(vertex) << endl; } return 0; }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值