// 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;
}
UVa Problem 10160 Servicing Stations (服务站)
最新推荐文章于 2020-07-04 13:12:01 发布