算法思路:回溯,暴力。
以下转载了两个版本:
版本1
0.032msAC。
// 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;
}
转载自:
http://blog.csdn.net/metaphysis/article/details/6601365
版本2
1.225msAC。
题目大意:给出n个点和m个关系,可以在任何一个点放服务站,如果这个点放了服务站,与它相连得点均可以被服务到,问最少放多少个服务站可以使得所有所有点均可以被服务到。
解题思路:思路很简单,用DFS收索,关键就在与剪枝,我的主要优化在于两个地方。
首先按点得序号开始DFS, cur 表示当前访问得点。
<1>对于每个点,无非就是放与不放(注意这里不能单纯根据这个点有没有被覆盖到去判断该不该放服务站)
<2>第一个剪枝,如果这个点增加服务站之后,被覆盖得点数没有增加,就可以确定当前点是不放(与放得情况相同干嘛要多加一个点)
<3>第二个剪枝,当放得服务站个数大于前面计算的最小值时,剪掉这条路。
<4>第三个剪枝,当出现前面有的点无法被覆盖得时候,可以终止这条路得收索。
这里问解释一下第三个剪枝得情况,比如当前已经访问到第5个点,而1 这点任然没有被覆盖到,而与1 有联系得点2,2 < 5, 说明2 已经被访问过了,说明1 和 2 都没有放服务站,而后面n个点得放与不放都影响不到1 得覆盖状态,所以这条路是无法被满足的。
我在将一下问对优化3的一个小优化,如果按正常的思路,需要开一个数组去记录哪些点放了服务站,然后进行优化3得时候对判断点要去遍历判断点的所有有关系的点是否已经考虑过。而我得做法是将点得关系点排序,每次只需将最大的点与当前访问的点进行比较,大于得话说明这个点还没被确定。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define N 40
int n, m, Min, rec[N];
int g[N][N], son[N];
int cmp(const int &a, const int &b){
return a > b;
}
void DFS(int cur, int cnt, int sum){
if (sum >= Min) // 剪枝2:当放得服务站数量大于前面计算的最小值时。
return;
if (cnt == n)
Min = sum;
for (int i = 1; i < cur; i++) //剪枝3:出现已经遍历的点无法被覆盖,这条路继续收索是无用功。
if (!rec[i] && g[i][0] < cur)
return;
DFS(cur + 1, cnt, sum);
int k = 0, vis[N];
for (int i = 0; i < son[cur]; i++)
if (rec[g[cur][i]] == 0){
vis[k++] = g[cur][i];
rec[g[cur][i]] = 1;
}
if (!k) // 剪枝1:增加覆盖点为0。
return ;
DFS(cur + 1, cnt + k, sum + 1);
for (int i = 0; i < k; i++) // 回溯要将点还原,不然答案会错。
rec[vis[i]] = 0;
}
int main(){
int a, b;
while (scanf("%d%d", &n, &m), n + m){
// Init;
memset(g, 0, sizeof(g));
memset(son, 0, sizeof(son));
memset(rec, 0, sizeof(rec));
Min = n + 1;
// Read;
for (int i = 0; i < m; i++){
scanf("%d%d", &a, &b);
g[a][son[a]++] = b;
g[b][son[b]++] = a;
}
// Handle;
for (int i = 1; i <= n; i++){
g[i][son[i]++] = i;
sort(g[i], g[i] + son[i], cmp); // 优化3的优化,具体看解释。
}
DFS(1, 0, 0);
printf("%d\n", Min);
}
return 0;
}
转载自:
点击打开链接