二进制状态压缩式枚举
对于一个序列,如果其包含的元素仅存在两种状态,那么它可以用二进制状态压缩。具体的操作如下:
| 操作 | 运算 |
|---|---|
| 取出整数 \(n\) 在二进制表示下的第 \(k\) 位 | (n>>k)&1 |
| 取出整数 \(n\) 在二进制表示下的第 \(0\)~\(k-1\) 位(后 \(k\) 位) | n&((1<<k)-1) |
| 把整数 \(n\) 在二进制表示下的第 \(k\) 位取反 | n xor (1<<k) |
| 对整数 \(n\) 在二进制表示下的第 \(k\) 位赋值 \(1\) | n|(1<<k) |
| 对整数 \(n\) 在二进制表示下的第 \(k\) 位赋值 \(0\) | n&(~(1<<k)) |
选自《算法竞赛进阶指南》
这种方法可以大幅减少程序运行的时间和空间常数。
压缩操作
可以二进制状态压缩的序列,有时候它是执行对象,它也可以是执行操作。对这一类序列的操作只有一种:选择部分元素取反。 (还能有什么其他的吗?)
所以我们可以把对整个对象的操作压缩成一个二进制整数 \(opt\):
(opt>>i)&1 == 0 表示要对第 \(i\) 位取反,反之不取反。
允许的情况下,我们把所有可能的操作压缩出来,得到一个序列 \(D\),那么,对整个对象 \(n\) 执行第 \(i\) 种操作只需要将 \(d_i\) 与 \(n\) 异或即可。
超级枚举!
通常我们枚举一个有较多元素(比如 25 个)的对象使用递归求解。但是二进制状态压缩提供了剪短代码的思路。我们枚举可能的操作压缩后对应的整数。这个可以结合例题来看。
费解的开关
这个题显然符合我们刚才所说的规律:我们处理的元素只有 开/关 两种状态。用一个 32 位整型变量(int)来储存要进行的操作。正如蓝书中所说:

但是怎么实现最快最简单呢?采用上文所说的方法。
- 获取所有压缩过后的操作,这里可以打表实现。代码:
#include<iostream>
using namespace std;
signed main(){
for(int i=1;i<=5;i++)
for(int j=1;j<=5;j++){
bool mat[7][7]={};int ans=0;
mat[i][j]=mat[i-1][j]=mat[i+1][j]
=mat[i][j-1]=mat[i][j+1]=1;
for(int t=0;t<25;t++)
ans+=(mat[t/5+1][t-t/5*5+1]<<t);
cout<<ans<<',';
}
return 0;
}
- 依次枚举操作序列。
for(int now=0,orin;now<32;now++)
这里的 now 指的就是操作序列。
- 检验该操作的合法性即可。
整体代码如下:
#include<bits/stdc++.h>
using namespace std;
int cnt,Q,d[25]={35,71,142,284,536,1121,2274,4548,
9096,17168,35872,72768,145536,291072,549376,
1147904,2328576,4657152,9314304,17580032,3178496,
7405568,14811136,29622272,25690112};
signed main(){
scanf("%d",&Q);
while(Q--){
char s;
int ori=0,mcnt=26;
for(int i=0;i<25;i++)
cin>>s,ori+=!int(s-'0')<<i;
for(int now=0,orin;now<32;now++){
orin=ori,cnt=0;
for(int i=0;i<5;i++)
if((now>>i)&1)orin^=d[i],cnt++;
for(int i=0;i<20;i++)
if((orin>>i)&1)
orin^=d[i+5],cnt++;
if(orin==0&&cnt<mcnt)mcnt=cnt;
} if(mcnt>6)puts("-1");
else printf("%d\n",mcnt);
}
return 0;
}
The Pilots Brothers' refrigerator
代码:
#include<cstdio>
#include<iostream>
using namespace std;
int anscnt=17,ans,tar;
int dir[16]={4383,8751,17487,34959,4593,8946,17652,35064,7953,12066,20292,36744,61713,61986,62532,63624};
signed main(){
char s;
for(int i=0;i<16;i++){
cin>>s;
tar+=(s=='+'?1<<i:0);
}
for(int now=0;now<(1<<16);now++){
int cnt=0,nt=tar;
for(int j=0;j<16;j++)
if((now>>j)&1){
nt^=dir[j];
cnt++;
}
if(nt==0&&cnt<=anscnt){
anscnt=cnt;
ans=now;
}
}
printf("%d\n",anscnt);
for(int i=0;i<16;i++)
if((ans>>i)&1)
printf("%d %d\n",i/4+1,i-i/4*4+1);
return 0;
}

浙公网安备 33010602011771号