PC/UVa:110804/10160
一家公司要在N个小镇里销售电脑,小镇之间有M条道路。公司决定在某些小镇建立服务站,保证对于任意一个小镇,要么有服务站,要么和它相邻的小镇有服务站。
这道题要枚举所有的子集才能找到最小解,书上明确指明了NPC问题,所以只能穷举搜索了,关键是如何剪枝。
第一种剪枝方法是从个数小的子集开始枚举,这样肯定能最快得到解。但是这种方法在uDebug上有些测试用例很慢。慢的原因是因为在搜索到叶节点后才判断是否有解。
接下来的剪枝方法是在搜索过程中就做一些关于是否有解的判断。可以在搜索的过程中同时记录当前哪些小镇满足要求(代码中的cover),那么在扩展下一个小镇时,如果对当前的覆盖率没有影响,那么就跳过这个小镇。这样的剪枝方法使得uDebug上UVa Online Judge的测试用例很快就算出来了。
但是uDebug上sergey_krusch的测试用例依然很慢,慢的原因是因为其中一个测试用例有34个小镇,但是存在17个连通分量,每个连通分量中只有两个小镇,可知搜索大小为1到16的子集就花费了很长时间。根据经验,图中点的数目越少,搜索的越快,所以就又使用宽搜divideGraph把原图拆成了多个连通分量,这样这个测试用例就比较快了。
此时代码已经可以AC了,时间1.310。
但是uDebug上sergey_krusch的下一个测试用例依然很慢。这个例子中有35小镇,连成了一条链,所以连通分量的剪枝方法没什么用。这里参考了一下网上的方法,即使用calBackCover提前计算包括当前城镇在内的剩下的所有城镇的覆盖率。如果选取当前剩下的所有城镇可以达到要求,就继续往下搜。这样对于上述的链式图,从第3个节点开始的所有搜索就都被剪掉了。
这是时间降到了0.010。
#include <iostream>
#include <vector>
#include <deque>
#include <bitset>
#define MAX_N 35
using namespace std;
void divideGraph(const vector<bitset<MAX_N>> &graph,
vector<vector<bitset<MAX_N>>> &vecChildGraph)
{
size_t N = graph.size();
vector<size_t> vecVisited(N, false);
for (size_t i = 0; i < N; i++)
{
if (!vecVisited[i]){
vector<bitset<MAX_N>> ChildGraph;
deque<size_t> deQueue;
deQueue.push_back(i);
vecVisited[i] = true;
size_t front;
while (!deQueue.empty()){
front = deQueue.front();
deQueue.pop_front();
ChildGraph.push_back(graph[front]);
for (size_t pos = 0; pos < N; pos++)
{
if (graph[front].test(pos) && !vecVisited[pos]){
deQueue.push_back(pos);
vecVisited[pos] = true;
}
}
}
vecChildGraph.push_back(ChildGraph);
}
}
}
void calBackCover(vector<vector<bitset<MAX_N>>> &vecChildGraph,
vector<vector<bitset<MAX_N>>> &vecBackCover)
{
for (size_t idx = 0; idx < vecChildGraph.size(); idx++)
{
const vector<bitset<MAX_N>> &ChildGraph = vecChildGraph[idx];
size_t node = ChildGraph.size();
vecBackCover.push_back(vector<bitset<MAX_N>>(node, bitset<MAX_N>()));
bitset<MAX_N> BackCover;
for (; node > 0; node--)
{
vecBackCover[idx][node - 1] = ChildGraph[node - 1] | BackCover;
BackCover = vecBackCover[idx][node - 1];
}
}
}
void DFS(const vector<bitset<MAX_N>> &graph,
const vector<bitset<MAX_N>> &back, bitset<MAX_N> &cover,
size_t curr, size_t cnt, const size_t bound, size_t &num)
{
if (cnt == bound){
if (cover.count() == graph.size()){
num = cnt;
}
return;
}
bitset<MAX_N> newCover;;
for (size_t i = curr; i < graph.size(); i++)
{
newCover = cover;
newCover |= back[curr];
if (newCover.count() != graph.size()) continue;
newCover = cover;
newCover |= graph[i];
if (newCover == cover) continue;
DFS(graph, back, newCover, i + 1, cnt + 1, bound, num);
if (num != 0) break;
}
}
int main()
{
size_t N = 0, M = 0;
while (cin >> N >> M){
if (N == 0 && M == 0) break;
vector<bitset<MAX_N>> graph(N, bitset<MAX_N>());
for (size_t v = 0; v < N; v++)
{
graph[v].set(v);
}
size_t v1, v2;
for (size_t m = 0; m < M; m++)
{
cin >> v1 >> v2;
graph[v1 - 1].set(v2 - 1);
graph[v2 - 1].set(v1 - 1);
}
vector<vector<bitset<MAX_N>>> vecChildGraph;
vector<vector<bitset<MAX_N>>> vecBackCover;
divideGraph(graph, vecChildGraph);
calBackCover(vecChildGraph, vecBackCover);
size_t total = 0;
for (size_t idx = 0; idx < vecChildGraph.size(); idx++)
{
const vector<bitset<MAX_N>> &ChildGraph = vecChildGraph[idx];
const vector<bitset<MAX_N>> &back = vecBackCover[idx];
bitset<MAX_N> cover;
size_t num = 0;
for (size_t bound = 1; bound <= ChildGraph.size(); bound++)
{
DFS(ChildGraph, back, cover, 0, 0, bound, num);
if (num != 0) break;
}
total += num;
}
cout << total << endl;
}
return 0;
}
/*
8 12
1 2
1 6
1 8
2 3
2 6
3 4
3 5
4 5
4 7
5 6
6 7
6 8
0 0
*/
本文探讨了一家公司在多个小镇间建立服务站的问题,确保每个小镇自身或其相邻小镇至少有一个服务站。通过深入分析,提出了一种基于穷举搜索的算法,并结合剪枝技巧和连通分量分析,有效降低了搜索复杂度,最终实现了高效求解。





