814 字
4 分钟
BZOJ 1770 [Usaco2009 Nov]lights 燈 折半搜索
2017-09-17

题目描述#

贝希和她的闺密们在她们的牛棚中玩游戏。但是天不从人愿,突然,牛棚的电源跳闸了,所有的灯都被关闭了。贝希是一个很胆小的女生,在伸手不见拇指的无尽的黑暗中,她感到惊恐,痛苦与绝望。她希望您能够帮帮她,把所有的灯都给重新开起来!她才能继续快乐地跟她的闺密们继续玩游戏!

牛棚中一共有N(1 <= N <= 35)盏灯,编号为1到N。这些灯被置于一个非常复杂的网络之中。有M(1 <= M <= 595)条很神奇的无向边,每条边连接两盏灯。每盏灯上面都带有一个开关。当按下某一盏灯的开关的时候,这盏灯本身,还有所有有边连向这盏灯的灯的状态都会被改变。状态改变指的是:当一盏灯是开着的时候,这盏灯被关掉;当一盏灯是关着的时候,这盏灯被打开。问最少要按下多少个开关,才能把所有的灯都给重新打开。数据保证至少有一种按开关的方案,使得所有的灯都被重新打开。

输入#

*第一行:两个空格隔开的整数:N和M。

*第二到第M+1行:每一行有两个由空格隔开的整数,表示两盏灯被一条无向边连接在一起。没有一条边会出现两次。

输出#

第一行:一个单独的整数,表示要把所有的灯都打开时,最少需要按下的开关的数目。

样例输入#

5 6
1 2
1 3
4 2
3 4
2 5
5 3

輸入細節:

一共有五盞燈。燈1、燈4和燈5都連接著燈2和燈3。

样例输出#

3

輸出細節:

按下在燈1、燈4和燈5上面的開關。

题解#

【折半搜索】
将需要搜索的元素分为不关联的两个部分,分别搜索

在本题中 N的范围有35如果直接搜的话2352^35会T的很惨
所以我们将N分为前后两部分
现将前mid个搜出来,记录每个状态集合的最小的答案
之后出来后mid + 1到n搜完后加上他的补集跟新答案

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
long long A[36], full, bin[37];
int Min = 0x3f, n, m;
map<long long, int> Mp;
void dfs1(int x, long long now, int k)
{
	if(x == m + 1)
	{
		if(now == full)
			Min = min(k, Min);
		int t = Mp[now];
		if(!t || t > k)
			Mp[now] = k;
		return;
	}
	dfs1(x + 1, now, k);
	dfs1(x + 1, now^A[x], k + 1);
}
void dfs2(int x, long long now, int k)
{
	if(x == n + 1)
	{
		if(now == full)
			Min = min(k, Min);
		int t = Mp[full - now];
		if(!t)
			return;
		Min = min(t + k, Min);
		return;
	}
	dfs2(x + 1, now, k);
	dfs2(x + 1, now^A[x], k + 1);
}
int main()
{
	// freopen("lights.in","r",stdin);
	// freopen("lights.out","w",stdout);
	int c;
	scanf("%d%d", &n, &c);
	bin[1] = 1;
	for (int i = 2; i < 37; i++)
		bin[i] = bin[i - 1] << 1;
	int a, b;
	for (int i = 1; i <= c; i++)
	{
		scanf("%d%d", &a, &b);
		A[a] += bin[b];
		A[b] += bin[a];
	}
	for (int i = 1; i <= n; i++)
		A[i] += bin[i];
	full = bin[n + 1] - 1;
	m = n >> 1;
	dfs1(1, 0, 0);
	dfs2(m + 1, 0, 0);
	printf("%d", Min);
}
BZOJ 1770 [Usaco2009 Nov]lights 燈 折半搜索
https://www.nekomio.com/posts/108/
作者
NekoMio
发布于
2017-09-17
许可协议
CC BY-NC-SA 4.0