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\) 了!

浙公网安备 33010602011771号