// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css //目录导航 //生成目录索引列表

P2962 [USACO09NOV] Lights G

题目描述

奶牛 Bessie 和其他奶牛在谷仓里玩游戏,但电源重置后所有灯都熄灭了。请帮助它们将所有的灯重新打开,以便继续游戏。

\(N\)\(1 \le N \le 35\))盏灯,编号为 \(1\)\(N\),它们的开关通过 \(M\)\(1 \le M \le 595\))条连接构成一个复杂的网络。

每盏灯都有一个开关,当你切换它时,该灯及所有与之直接相连的灯都会改变状态(开变关,关变开)。

请找出最少需要切换多少次开关,才能把所有灯都打开。

题目保证至少存在一种切换方案可以将所有灯点亮。

输入格式

\(1\) 行是两个用空格分隔的整数 \(N\)\(M\)

\(2\) 行到第 \(M + 1\) 行,每行两个空格分隔的整数,表示一条连接的两个灯的编号(无重复)。

输出格式

仅一行一个整数,表示将所有灯点亮所需的最少开关切换次数。

输入输出样例 #1

输入 #1

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

输出 #1

3

说明/提示

样例解释

\(5\) 盏灯中,灯 \(1, 4, 5\) 各自都与灯 \(2\) 和灯 \(3\) 相连。

只需切换灯 \(1, 4, 5\) 的开关,即可使所有灯都打开。

基本思路

这题乍一看是不是一个树形 \(DP\)

这不禁让我回想起了战略游戏那一道题。

实则不然。

真转移的话是不是太恶心了?越复杂的解法越容易出错。

所以我们换一种思考方式,你看开关灯像不像一个异或?那么好,我们将一个灯被处理两次视作异或两次 \(1\)

那更好了,我们回想起一个数被异或两次 \(1\)的话相当于没有被操作过,所以我们要将一个灯处理奇数次,那么同时意味着一个节点只有可能选或不选,没有可能选一次以上。

那真的是太好了,我们是不是可以将一个节点所有相邻的节点用二进制储存起来,然后选数执行异或?

是的,我们就将这个问题转化成了一个选或不选的问题?

是不是能用背包做?好像不行,我不知道。

那就搜索吧,但好像这题没有记忆化的条件。但是 \(35\) 这个数字对我们而言真的是太熟悉了,那就折半搜索吧。

那么第一版代码就出场了,是的,我为了提高效率选择了数组储存, \(map\) 实在太慢了,毕竟我们只用对前一半进行处理,产生一半的状态完全可以接受!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 40;
ll n, m, g[N], flag, ans;
ll graph[1 << 20];

void dfs1(int p, int to, int step, int s) {
	if (graph[s] < 0)
		graph[s] = step;
	else
		graph[s] = min(graph[s], (ll)step);
	if (p > to)
		return;
	dfs1(p + 1, to, step + 1, (s ^g[p]));
	dfs1(p + 1, to, step, s);
}

void dfs2(int p, int to, int step, int s) {
	int tmp = flag ^s;
	if (tmp < (1 << 20) && graph[tmp] >= 0) {
		ans = min(ans, step + graph[tmp]);
	}
	if (p > to)
		return;
	dfs2(p + 1, to, step + 1, s ^g[p]);
	dfs2(p + 1, to, step, s);
}

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> m;
	memset(graph, -1, sizeof(graph));
	for (int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		g[u] |= (1 << (v - 1));
		g[v] |= (1 << (u - 1));
	}
	flag = (1 << n) - 1;
	int mid = n >> 1;
	if (mid > 1) {//小心除完后是什么
		ans = 0x3f3f3f3f;
		dfs1(1, mid, 0, 0);
		dfs2(mid + 1, n, 0, 0);
		cout << ans;
		return 0;
	} else
		dfs1(1, n, 0, 0);
	cout << graph[flag];
	return 0;
}

首先,样例为什么错了。

而且我们发现错误答案要么是个一模一样的极大值,要么是 \(0\) !说明根本没有搜到合法解!

好的,只储存了相邻节点,根本没有储存自己!

for (int i = 1; i <= n; i++)
	g[i] = (1 << (i - 1));

好的,补上了。

但为什么 \(RE\) 了。是的自作聪明的优化!

我们虽然只需要搜前一半,但产生的状态可不止前一半!

还是要用 \(map\)

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 40;
ll n, m, g[N], flag, ans;
map<ll, ll>graph;

void dfs1(ll p, ll to, ll step, ll s) {
	if (graph.count(s) == 0ll)
		graph[s] = step;
	else
		graph[s] = min(graph[s], step);
	if (s == flag)
		ans = min(ans, step);
	if (p > to)
		return;
	dfs1(p + 1ll, to, step + 1ll, (s ^g[p]));
	dfs1(p + 1ll, to, step, s);
}

void dfs2(ll p, ll to, ll step, ll s) {
	ll tmp = flag ^s;
	if (graph.count(tmp)) {
		ans = min(ans, step + graph[tmp]);
	}
	if (s == flag)
		ans = min(ans, step);
	if (p > to)
		return;
	dfs2(p + 1ll, to, step + 1ll, s ^g[p]);
	dfs2(p + 1ll, to, step, s);
}

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (ll i = 1; i <= n; i++)
		g[i] = (1ll << (i - 1ll));
	for (ll i = 1; i <= m; i++) {
		ll u, v;
		cin >> u >> v;
		g[u] |= (1ll << (v - 1ll));
		g[v] |= (1ll << (u - 1ll));
	}
	flag = (1ll << n) - 1ll;
	ll mid = n >> 1ll;
	if (mid > 1ll) {
		ans = 0x3f3f3f3f;
		dfs1(1ll, mid, 0ll, 0ll);
		dfs2(mid + 1ll, n, 0ll, 0ll);
		cout << ans;
		return 0;
	} else
		dfs1(1ll, n, 0ll, 0ll);
	cout << graph[flag];
	return 0;
}

等等!

看看上面一串 \(1ll\)\(35\) 位的左移操作会导致 \(int\) 溢出!不用 \(ll\) 就默认是 \(int\) 了!

posted @ 2025-08-20 17:59  SSL_LMZ  阅读(11)  评论(0)    收藏  举报