前言

今天整理以前的竞赛笔记时,发现了当时写的一个模板:

枚举{0,1,…,n-1}所包含的所有大小为k的子集:

int comb = (1 << k) - 1;
while (comb < 1 << n) {
//进行针对组合的处理
int x = comb & -comb, y = comb + x;
comb = ((comb&~y) / x >> 1) | y;
}

我愣是看了半天,也没想明白当时我想表达什么(lll¬ω¬)

然后就百度了一下,结合一些描述,终于想起来这貌似是从小白书上扒下来的

话说我小白书已经失踪一年了,到现在还没找到......

以防以后又把它忘了,特此记录

什么是“枚举{0,1,…,n-1}所包含的所有大小为k的子集”

“枚举{0,1,…,n-1}所包含的所有大小为k的子集”与二进制状态压缩关系密切,其本质为利用二进制位元算表示和操作集合,举个例子:

含有n个元素的集合{0,1,…,n-1},就有n个二进制位,第i个二进制位代表第i个元素,第i个二进制位为1代表第i个元素存在于集合,第i位二进制位为0代表第i个元素不存在于集合。(i<=n)

含有3个元素的集合{0,1,2},全部子集有00000001001000110100010101100111,其中0000代表空集

二进制数与集合对应关系如下:

我们不难得出,枚举集合{0,1,…,n-1}的所有子集的方法:

for (int S = 0; S < 1 << n; S++) {
//对子集的操作
}

S < 1 << n等同于S <= ( (1<<n)-1 ),(1<<n)-1为含有n个元素的集合{0,1,…,n-1}。

解决**“枚举{0,1,…,n-1}所包含的所有大小为k的子集”,我们只需弄清什么是“大小为k的子集”**。

**“大小为k的子集”**就是有k个元素的子集,也就是二进制中有k个1。

含有3个元素的集合{0,1,2}所包含的所有大小为2的子集:

如何枚举?

为了将所有情况枚举出来,我们可以枚举集合{0,1,…,n-1}的所有子集,在枚举时加入判断,判断当前子集是否满足**“大小为k的子集”**。

从实现上来看,这是可行的:

int n, k;
int getsum(int S) {// 统计二进制中1的个数
int ans = 0;
while (S){
if (S & 1)
ans++;
S >>= 1;
}
return ans;
}
for (int S = 0; S < 1 << n; S++) {
if (getsum(S) == k) {
// 对子集的操作
}
}

但这不够优秀,不如说相当低效,这时我们需要找到一种更优秀的枚举方法。

白书上提供了一种思路:

int comb = (1 << k) - 1;
while (comb < 1 << n) {
//进行针对组合的处理
int x = comb & -comb, y = comb + x;
comb = ((comb&~y) / x >> 1) | y;
}

comb是按字典序排列的最小子集,在while循环中,comb会一直增大,直到找完所有大小为k的子集

我们利用刚刚的例子,来模拟算法找**“含有3个元素的集合{0,1,2}所包含的所有大小为2的子集”**的过程:

(此例中 k=2,n=3)

第一次循环,我们找到了按字典序排列的最小子集,也就是comb的初始值0011,之后comb**“按算法提供方法”**增大,comb的值变为0101

第二次循环,我们找到的是0101,之后comb**“按算法提供方法”**增大,comb的值变为0110

第三次循环,我们找到的是0110,之后comb**“按算法提供方法”**增大,comb的值变为1001

此时,comb的值不满足**“comb < 1<<n即不满足"1001 < 1000"**,算法结束于第四次循环的开始。

**“按算法提供方法”**也就是每次求下一个子集的方法如下:

(以1100 1100到其下一个子集1101 0001为例)

int comb = (1 << k) - 1;
while (comb < 1 << n) {
//进行针对组合的处理
int x = comb & -comb, y = comb + x;
comb = ((comb&~y) / x >> 1) | y;
}

我们将核心代码提取并拆解:

    int x = comb & -comb; //步骤(2)
int y = comb + x; //步骤(3)
int z = comb & ~y; //步骤(1)
int b = (z / x) >> 1; //步骤(4),'z/x'相当于去掉右侧多余的0,'>>1'则使剩下的1的个数减少一个
comb = b | y; //步骤(5)

(1)取出字典序最小的1的连续区间,1100 1100 → 0000 1100

(2)找到字典序最小的1的位置,1100 1100 → 0000 0100

(3)将字典序最小的1的连续区间置为0,并将区间左侧第一个0置为1,1100 1100 → 1101 0000

(4)将 (1) 取出的区间右移,直至区间中1的个数减少一个,0000 1100 → 0000 0001

(5)将 (4) 的结果与 (3) 的结果取并集,0000 0001 | 1101 0000 → 1101 0001

按照这种方法,我们不难找出后续的子集:

1101 00101101 01001101 10001110 00011110 00101110 01001110 10001111 0000...

(正文完)

后记

发现这个算法人也太优秀了吧!!太巧妙了!

(小白书:挑战程序设计竞赛)

参考文献

weixin_30443075.集合的整数表示与子集枚举

XDU_Skyline.集合的表示及其运算

关于“枚举{0,1,...,n-1}所包含的所有大小为k的子集”的理解的更多相关文章

  1. 枚举大小为k的子集

    这种位操作不大可能分析出来,先看代码再分析. 代码 使用条件:\(k>0\) void solve(int n,int k) { for(int comb = (1 << k) - ...

  2. 【TRICK】[0,n)中所有大小为k的子集的方法

    << k) - ; <<n)) { int x = comb & -comb, y = comb + x; comb = (((comb & ~y)/x)> ...

  3. 如果 date_field = TRUNC(date_field) 就说明时分秒为0(也就是不包含),否则就包含时分秒

    如果 date_field = TRUNC(date_field) 就说明时分秒为0(也就是不包含),否则就包含时分秒

  4. Java上传图片到Ftp,包含上传后文件大小为0的问题和Properties配置文件的读取

    准备工作:需要使用coomos-net jar包.下载地址 一. 上传图片到FTP,文件大小为0的问题,解决:将ftp模式修改为Passive模式就可以了. //将ftp模式修改为Passive模式 ...

  5. 用webclient.DownloadFile下载exe文件时大小为0

    用自己写的下载软件从服务器端下载文件,别的文件能下,但exe文件显示下载文件大小为0,连接超时,原因是服务上发布的下载文件夹的虚拟目录的属性有问题, 包含.exe 文件的虚拟目录已启用执行应用程序权限 ...

  6. Linux Shell中的特殊符号和含义简明总结(包含了绝大部份)

    case语句适用于需要进行多重分支的应用情况. case分支语句的格式如下: case $变量名 in 模式1) 命令序列1 ;; 模式2) 命令序列2        ;; *) 默认执行的命令序列  ...

  7. 求包含每个有序数组(共k个)至少一个元素的最小区间

    title: 求包含每个有序数组(共k个)至少一个元素的最小区间 toc: false date: 2018-09-22 21:03:22 categories: OJ tags: 归并 给定k个有序 ...

  8. 为什么HashMap初始大小为16,为什么加载因子大小为0.75,这两个值的选取有什么特点?

    先看HashMap的定义: public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V> ...

  9. 【FTP】java FTPClient 文件上传内容为空,文件大小为0

    问题:如题所述,使用FTPClient上传至FTP服务器, 表现如下:①文件大小为0 ②上传很小的文件,但是要花费很长的时间,20K要花费2分钟甚至更久 ③没有任何的报错,没有任何的乱码 解决方法: ...

随机推荐

  1. C#LeetCode刷题之#819-最常见的单词(Most Common Word)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3969 访问. 给定一个段落 (paragraph) 和一个禁用单 ...

  2. C#LeetCode刷题之#283-移动零(Move Zeroes)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3907 访问. 给定一个数组 nums,编写一个函数将所有 0 移 ...

  3. Vue 引入指定目录文件夹所有的组件 require.context

    require.context require.context是webpack中用来管理依赖的一个函数,此方法会生成一个上下文模块,包含目录下所有的模块的引用,同构正则表达式匹配,然后require进 ...

  4. 题解 UVA10457

    题目大意:另s = 路径上的最大边权减最小边权,求u到v上的一条路径,使其s最小,输出这个s. 很容易想到枚举最小边然后跑最小瓶颈路. so,如何跑最小瓶颈路? 利用Kruskal,因为树上两点路径唯 ...

  5. 轻轻松松学CSS:position

    position属性表示元素的定位类型,在CSS布局中,position发挥着非常重要的作用,一些元素的布局就是用position完成的,鉴于此,本文结合一些小实例详细讲解一下. position属性 ...

  6. 微信小程序内置组件web-view的缓存问题探讨

    前言:博客或者论坛上面,还有自习亲身经历,发现微信小程序的webview组件的页面缓存问题相当严重,对开发H5的小童鞋来说应该困扰了不少.很多小童鞋硬是抓破脑袋也没有办法解决这个问题,那我们今天就来探 ...

  7. TypeScript 安装及基础运行环境搭建

    安装 TypeScript npm install -g typescript 安装成功后,命令行输入 tsc -v 命令提示当前安装的 TypeScript 版本: 出现版本号即安装成功. 使用 V ...

  8. Linux服务器上创建新用户

    一.在/home目录下新建userName目录 sudo useradd -m -s /bin/bash userName 二.设置密码 sudo passwd userName

  9. latex:备忘代码

    1.脚注代码: \footnote{欧几里德,约公元前330--340年.} 结果为: 2.改变某个字的字体形状,表示强调 \emph{勾股定理} 结果为(勾股数这三个字与其他字不一样): 3.正文中 ...

  10. Clayff力场(转载)

    本文转载自:http://blog.sina.com.cn/s/blog_b48a7ac30102w4km.html 注意:lammps中并没有完整的clayff势函数,需要将一下三者结合使用. an ...