二进制状态压缩式枚举

对于一个序列,如果其包含的元素仅存在两种状态,那么它可以用二进制状态压缩。具体的操作如下:

操作 运算
取出整数 n 在二进制表示下的第 k (n>>k)&1
取出整数 n 在二进制表示下的第 0~k1 位(后 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 种操作只需要将 din 异或即可。

超级枚举!

通常我们枚举一个有较多元素(比如 25 个)的对象使用递归求解。但是二进制状态压缩提供了剪短代码的思路。我们枚举可能的操作压缩后对应的整数。这个可以结合例题来看。

费解的开关


AcWing95

这个题显然符合我们刚才所说的规律:我们处理的元素只有 开/关 两种状态。用一个 32 位整型变量(int)来储存要进行的操作。正如蓝书中所说:

《算法竞赛进阶指南》- 费解的开关

但是怎么实现最快最简单呢?采用上文所说的方法。

  1. 获取所有压缩过后的操作,这里可以打表实现。代码:
#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;
}

  1. 依次枚举操作序列。
for(int now=0,orin;now<32;now++)

这里的 now 指的就是操作序列。

  1. 检验该操作的合法性即可。

整体代码如下:

#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


POJ2965 / AcWing116

代码:

#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;
}

posted @   robinyqc  阅读(70)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示