状态压缩中枚举子集
状态压缩中枚举子集
前言
在状态压缩中,有时会遇到集合间转移的题目,而如何找到某个集合的所有子集,成为了解决问题的关键。
正文
首先,对于一个集合
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; }
但这样有个显然的问题,就是太慢了。有没有快一点的方法判断是否为子集呢?
答案是判断一下
那么有
bool is_subset(int s, int x){ return (x&s)==x; }
好,现在我们有了快速判断子集的方法!那么,我们以
00011010 00011000 00010010 00010000 00001010 00001000 00000010 00000000
怎么样,是不是不重不漏~
emm,但是你还是感觉太慢了。如果
我们发现,当我们枚举时,有很多冗余的状态——
->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 //箭头指出的是合法子集
我们发现,对于每一个合法的
为什么这样是正确的呢?因为减去
可以发现,这样枚举也是不重不漏的。
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
这样子,枚举的复杂度就变为
#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; }