状态压缩中枚举子集

状态压缩中枚举子集

前言

在状态压缩中,有时会遇到集合间转移的题目,而如何找到某个集合的所有子集,成为了解决问题的关键。

正文

首先,对于一个集合 s,我们可以通过它转换为二进制数后的 01 来表示一些元素的状态。如:选择和未选,经过与未经过。下文的集合均指其压缩后的二进制数。很显然,对于 s 的子集,它的大小不会超过 s。因为超过以后,意味着一定有一个不在 s 中的 1。所以,我们的枚举范围就缩小到所有小于等于 s 的数。假设我们枚举到了集合 x,判断 x 是否为 s 的一个子集最简单也是最暴力的方法:枚举 x 每一位,看是否有不在 s 中的元素。

bool is_subset(int s, int x){
for(int i = 0; i<8; i++){
if(((s>>i)&1)==0&&((x>>i)&1)==1){
return 0;
}
}
return 1;
}

但这样有个显然的问题,就是太慢了。有没有快一点的方法判断是否为子集呢?

答案是判断一下 x & s 是否等于 x 即可。为什么这样是对的呢?因为与操作可以将 x 中所有在 s 中为 0 的位置上的 1 抹去,而为 1 的位置上不变。

那么有

bool is_subset(int s, int x){
return (x&s)==x;
}

好,现在我们有了快速判断子集的方法!那么,我们以 s=11010 为例,看一看枚举的效果——

00011010
00011000
00010010
00010000
00001010
00001000
00000010
00000000

怎么样,是不是不重不漏~

emm,但是你还是感觉太慢了。如果 s 太大,那枚举的次数可是 2n 级别的!

我们发现,当我们枚举时,有很多冗余的状态——

->00011010
00011001
->00011000
00010111
00010110
00010101
00010100
00010011
->00010010
00010001
->00010000
00001111
00001110
00001101
00001100
00001011
->00001010
00001001
->00001000
00000111
00000110
00000101
00000100
00000011
->00000010
00000001
->00000000
//箭头指出的是合法子集

我们发现,对于每一个合法的 x,它的下一个合法子集(设为 y ),总是小于 x,而中间的所有状态(设为 q)都满足 y<q<x,那我们不妨直接去生成这个 y。于是有 y=s & (x1)

为什么这样是正确的呢?因为减去 1 会让 x 最后一个 1 变为 0,而它之后的所有位变成 1。与 s 做与运算后,相当于消去最后一个 1,然后再去枚举这个 1 之后的二进制数的子集。

可以发现,这样枚举也是不重不漏的。

void find_subset(int s){
int tmp = s;
print(s);
while(tmp){
tmp = (tmp-1)&s;
print(tmp);
}
}

输出如下——

00011010
00011000
00010010
00010000
00001010
00001000
00000010
00000000

这样子,枚举的复杂度就变为 2k1 了,其中 k 是集合中元素的数量。

#include<bits/stdc++.h>
using namespace std;
void print(int x){
for(int i = 7; i>=0; i--){
printf("%d", (x>>i)&1);
}
putchar('\n');
}
bool is_subset(int s, int x){
return (x&s)==x;
}
//bool is_subset(int s, int x){
// for(int i = 0; i<8; i++){
// if(((s>>i)&1)==0&&((x>>i)&1)==1){
// return 0;
// }
// }
// return 1;
//}
void find_subset(int s){
int tmp = s;
print(s);
while(tmp){
tmp = (tmp-1)&s;
print(tmp);
}
}
int main(){
int x = 0b11010;
find_subset(x);
return 0;
}
posted @   霜木_Atomic  阅读(246)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
点击右上角即可分享
微信分享提示