UVA10160

题目的意思就是在图中建尽量少的服务站,使任一点都能在自己,或是相邻的点找到服务站。

一开始用普通的回溯搜,可以得出答案,但是超时了,剪了好多枝还是超时。所以去网上看了看题解,发现了一种用状态压缩的做法,觉得很神奇,就学着写了写。


状态压缩就是用二进制表示状态,好处就是可以用位运算操作,

像 st[i] 如果和 1 << 5 进行或运算,那么st[i] 第五个位置上就变成了1.,如5的二进制是0101 ,可以表示在这个点建站那么城市1 和 3是可以连到的,如果这时发现城市2也可以连到,那么 在和 1 << (2 - 1) 也就是0010进行一次或运算 ,这样结果就变成0111 。


第一步`输入完得到每个城市如果建站,会有哪些城市可以用,也就是相邻。

while (cas--) {
			cin >> a >> b;
			st[a] |= ((ll)1 << (b - 1));
			st[b] |= ((ll)1 << (a - 1));
		}

然后用回溯,如果只建一座,如果建2座 ,到可以实现时输出,就是最小的。

for (int i = 1 ; i <= num ;i++) {
			if (dfs((ll)0,0,i,1)) {
				cout << i <<endl;
				break;
			}
		}

dfs第一个参数是当前的状态,也就是哪些城市符合要求(变成二进制时1的地方就是符合要求的地方),第二个参数是已经建了几座,第三个是要建几座,第四个参数是当前回溯要从哪个城市开始循环。

for (int i = k ; i <= num ;i++){
		if( (sta | vis[i]) != ((ll)1 << num) - 1)
			return false;
		if( (sta | st[i]) == sta) 
			continue;
		if( dfs ( (sta | st[i]) , now + 1 ,target , k + 1))
			return true;
	}

前两个if 都属于剪枝 ,第一个意思是如果当前状态,i 之后的城市全部建上,也没有办法达到全部符合,那就直接返回不行, 第二个if 表示加上这座城市,状态没有变化,那也没有递归的必要。然后是继续把状态加上,建的数量加一,递归


AC代码:


#include<iostream>
using namespace std;
#define ll long long
const int N = 35 + 5;
ll st[N];
ll vis[N];
int num,cas;
bool dfs(ll sta ,int now ,int target , int k ) {
	if (now == target) {
		if(sta == ((ll)1 << num) - 1)
			return true;
		else 
			return false;
	}
	for (int i = k ; i <= num ;i++){
		if( (sta | vis[i]) != ((ll)1 << num) - 1)
			return false;
		if( (sta | st[i]) == sta) 
			continue;
		if( dfs ( (sta | st[i]) , now + 1 ,target , k + 1))
			return true;
	}
	return false;
}
int main () {
	int a,b;
	while (cin >> num >> cas && num + cas) {
		for (int i = 0 ; i < N ;i++) {
			vis[i] = 0;
		}
		for (int i = 0 ; i < num ; i++) {
			st[i + 1] = ((ll)1 << i);
		}	
		while (cas--) {
			cin >> a >> b;
			st[a] |= ((ll)1 << (b - 1));
			st[b] |= ((ll)1 << (a - 1));
		}
		vis[num] = st[num];
		for (int i = num - 1; i > 0 ;i--) {
			vis[i] = vis[i + 1]|st[i];
		}
		for (int i = 1 ; i <= num ;i++) {
			if (dfs((ll)0,0,i,1)) {
				cout << i <<endl;
				break;
			}
		}
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值