状态压缩中枚举子集

您所在的位置:网站首页 二进制子集 状态压缩中枚举子集

状态压缩中枚举子集

2024-04-16 19:53| 来源: 网络整理| 查看: 265

状态压缩中枚举子集 前言

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

正文

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

bool is_subset(int s, int x){ for(int i = 0; i>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\) 太大,那枚举的次数可是 \(2^n\) 级别的!

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

->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\) & \((x-1)\)。

为什么这样是正确的呢?因为减去 \(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

这样子,枚举的复杂度就变为 \(2^k-1\) 了,其中 \(k\) 是集合中元素的数量。

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


【本文地址】


今日新闻


推荐新闻


    CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3