题目大意:
解题思路:
因为是第一次写折半搜索的博客,所以先明确什么是折半搜索
- 从状态图上起点和终点同时(但在程序上你无法并行计算,所以会有个先后顺序,先进行的搜索对于状态进行存储,后进行的搜索遇到已经存储过的状态则认为着相遇(最简单的相遇,不同题要不同讨论相遇情况))开始进行宽度/深度优先搜索(分别都只利用一半的状态),如果发现相遇了,那么可以认为是获得了可行解。
- 折半搜索往往运用在 O ( k n ) O(k^n) O(kn),使之减少到 O ( n k n / 2 ) O(nk^{n/2}) O(nkn/2)
对于这道题,首先利用位运算来存储每盏灯相关联的灯
for (int i = 0; i < n; i++) a[i] = (1ll << i);
ll u, v;
for (int i = 0; i < m; i++) {
cin >> u >> v;
u--, v--;
a[u] |= (1ll << v);
a[v] |= (1ll << u);
}
然后对于初始状态以及末状态,状态压缩后分别为:
0以及(1ll << n) - 1
因为一开始用2进制存储了每盏灯所关联的灯,则dfs中的状态传递就利用异或即可,异或代表开该盏灯,否则就是不开
之后分别进行两次搜索
- 第一次搜索,则从初状态开始搜索(当然也可以从末状态开始),只利用前一半(只要是一半就行,为了方便只利用前一半),对于搜索到的状态保存,并在此基础上存储每种状态的最优值(其实就是一半状态)
void dfs1(int x, ll t, ll k) {
if (x == n / 2) {
if (!mp.count(t)) mp[t] = k;
else mp[t] = min(mp[t], k); //存储最优值
return;
}
dfs1(x + 1, t, k),
dfs1(x + 1, t ^ a[x], k + 1);
}
- 第二次搜索,则从相反的状态开始,并利用剩下的一半,对于相遇的情况统计最优值即可
void dfs2(int x, ll t, ll k) {
if (x == n) {
if (mp.count(t))
ans = min(ans, k + mp[t]);
return;
}
dfs2(x + 1, t, k),
dfs2(x + 1, t ^ a[x], k + 1);
}
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f;
const int maxn = 1e2 + 10;
ll n, m, _n, ans = inf, a[maxn];
map <ll, ll> mp;//前半段的状态以及达到每种状态的最少按开关次数存储在 map 里面
void dfs1(int x, ll t, ll k) {
if (x == n / 2) {
if (!mp.count(t)) mp[t] = k;
else mp[t] = min(mp[t], k);
return;
}
dfs1(x + 1, t, k),
dfs1(x + 1, t ^ a[x], k + 1);
}
void dfs2(int x, ll t, ll k) {
if (x == n) {
if (mp.count(t))
ans = min(ans, k + mp[t]);
return;
}
dfs2(x + 1, t, k),
dfs2(x + 1, t ^ a[x], k + 1);
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) a[i] = (1ll << i);
ll u, v;
for (int i = 0; i < m; i++) {
cin >> u >> v;
u--, v--;
a[u] |= (1ll << v);
a[v] |= (1ll << u);
}
dfs1(0, 0, 0), dfs2(n / 2, (1ll << n) - 1, 0);
cout << ans << endl;
}