P2962 [USACO09NOV]灯Lights [高斯消元+异或方程组 / 折半搜索]
节日宴会上,我们有 N(10<=N<=36)盏彩色灯,他们分别从 1 到 N 被标上号码。有 M 条边连接着这些灯,当按下某一盏灯的开关的时候,这盏灯本身以及所有和这盏灯有边相连的灯的开关状态都会发生改变。最开始所有灯都是被关着的,问需要至少按下多少开关,才可以把所有灯打开。
.
同 这道题目 建立异或方程组, 对自由元的状态进行枚举, 解出方程, 从中选出最优解.
折半搜索,
将原来的序列分为 和 两部分,
从 始状态 搜出操作前半部分能得到的所有状态, 记为 ,
从 末状态 倒搜出操作后半部分能得到的所有状态, 记为 ,
在 中查找每个 中的元素, 找到则更新答案 .
注意 和 不要搞混 .
code\ with\ bug
使用状态压缩:
- 每个操作都可以使用二进制状态表示, 使用时直接对状态异或就可以方便地改变状态.
- 使用 保存状态.
- 注意自环 .
#include<bits/stdc++.h>
#define reg register
typedef long long ll;
const int maxn = 300005;
int N;
int M;
int Ans;
ll Opt[maxn];
std::map <ll, int> Mp;
void DFS(int k, int cnt, ll zt){
if(k == N+1){
if(Mp.count(zt)) Ans = std::min(Ans, cnt + Mp[zt]);
return ;
}
DFS(k+1, cnt+1, zt^Opt[k]), DFS(k+1, cnt, zt);
}
int main(){
scanf("%d%d", &N, &M);
for(reg int i = 1; i <= M; i ++){
int a, b;
scanf("%d%d", &a, &b);
Opt[a] |= (1ll<<b-1), Opt[b] |= (1ll<<a-1);
}
for(reg int i = 1; i <= N; i ++) Opt[i] |= (1ll<<i-1);
int Half = N >> 1;
for(reg int i = 0; i < 1<<Half; i ++){
int cnt = 0;
ll zt = 0;
for(reg int j = 1; j <= Half; j ++)
if((1ll<<j-1) & i) cnt ++, zt ^= Opt[j];
if(Mp.count(zt)) Mp[zt] = std::min(Mp[zt], cnt);
else Mp[zt] = cnt;
}
Ans = 0x3f3f3f3f;
DFS(Half+1, 0, (1ll<<N)-1);
printf("%d\n", Ans);
return 0;
}