Programming Challenges 习题8.6.4

本文探讨了一家公司在多个小镇间建立服务站的问题,确保每个小镇自身或其相邻小镇至少有一个服务站。通过深入分析,提出了一种基于穷举搜索的算法,并结合剪枝技巧和连通分量分析,有效降低了搜索复杂度,最终实现了高效求解。

PC/UVa:110804/10160

Servicing Stations

一家公司要在N个小镇里销售电脑,小镇之间有M条道路。公司决定在某些小镇建立服务站,保证对于任意一个小镇,要么有服务站,要么和它相邻的小镇有服务站。

这道题要枚举所有的子集才能找到最小解,书上明确指明了NPC问题,所以只能穷举搜索了,关键是如何剪枝。

第一种剪枝方法是从个数小的子集开始枚举,这样肯定能最快得到解。但是这种方法在uDebug上有些测试用例很慢。慢的原因是因为在搜索到叶节点后才判断是否有解。

接下来的剪枝方法是在搜索过程中就做一些关于是否有解的判断。可以在搜索的过程中同时记录当前哪些小镇满足要求(代码中的cover),那么在扩展下一个小镇时,如果对当前的覆盖率没有影响,那么就跳过这个小镇。这样的剪枝方法使得uDebug上UVa Online Judge的测试用例很快就算出来了。

但是uDebug上sergey_krusch的测试用例依然很慢,慢的原因是因为其中一个测试用例有34个小镇,但是存在17个连通分量,每个连通分量中只有两个小镇,可知搜索大小为116的子集就花费了很长时间。根据经验,图中点的数目越少,搜索的越快,所以就又使用宽搜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
*/

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值