关于“枚举{0,1,...,n-1}所包含的所有大小为k的子集”的理解
前言
今天整理以前的竞赛笔记时,发现了当时写的一个模板:
枚举{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},全部子集有0000、0001、0010、0011、0100、0101、0110、0111,其中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 0010、1101 0100、1101 1000、1110 0001、1110 0010、1110 0100、1110 1000、1111 0000...
(正文完)
后记
发现这个算法人也太优秀了吧!!太巧妙了!
(小白书:挑战程序设计竞赛)
参考文献
关于“枚举{0,1,...,n-1}所包含的所有大小为k的子集”的理解的更多相关文章
- 枚举大小为k的子集
这种位操作不大可能分析出来,先看代码再分析. 代码 使用条件:\(k>0\) void solve(int n,int k) { for(int comb = (1 << k) - ...
- 【TRICK】[0,n)中所有大小为k的子集的方法
<< k) - ; <<n)) { int x = comb & -comb, y = comb + x; comb = (((comb & ~y)/x)> ...
- 如果 date_field = TRUNC(date_field) 就说明时分秒为0(也就是不包含),否则就包含时分秒
如果 date_field = TRUNC(date_field) 就说明时分秒为0(也就是不包含),否则就包含时分秒
- Java上传图片到Ftp,包含上传后文件大小为0的问题和Properties配置文件的读取
准备工作:需要使用coomos-net jar包.下载地址 一. 上传图片到FTP,文件大小为0的问题,解决:将ftp模式修改为Passive模式就可以了. //将ftp模式修改为Passive模式 ...
- 用webclient.DownloadFile下载exe文件时大小为0
用自己写的下载软件从服务器端下载文件,别的文件能下,但exe文件显示下载文件大小为0,连接超时,原因是服务上发布的下载文件夹的虚拟目录的属性有问题, 包含.exe 文件的虚拟目录已启用执行应用程序权限 ...
- Linux Shell中的特殊符号和含义简明总结(包含了绝大部份)
case语句适用于需要进行多重分支的应用情况. case分支语句的格式如下: case $变量名 in 模式1) 命令序列1 ;; 模式2) 命令序列2 ;; *) 默认执行的命令序列 ...
- 求包含每个有序数组(共k个)至少一个元素的最小区间
title: 求包含每个有序数组(共k个)至少一个元素的最小区间 toc: false date: 2018-09-22 21:03:22 categories: OJ tags: 归并 给定k个有序 ...
- 为什么HashMap初始大小为16,为什么加载因子大小为0.75,这两个值的选取有什么特点?
先看HashMap的定义: public class HashMap<K,V>extends AbstractMap<K,V>implements Map<K,V> ...
- 【FTP】java FTPClient 文件上传内容为空,文件大小为0
问题:如题所述,使用FTPClient上传至FTP服务器, 表现如下:①文件大小为0 ②上传很小的文件,但是要花费很长的时间,20K要花费2分钟甚至更久 ③没有任何的报错,没有任何的乱码 解决方法: ...
随机推荐
- Quartz.Net的基础使用方法,多任务执行继续扩展
前一篇随笔讲了Quartz多任务的简单实现 Quartz.Net的基础使用方法,多任务执行 这一篇,来简单对前一篇进行一下简单的扩展 看了前一篇的代码会发现,每次新增一个任务还要去GetJobs方法里 ...
- 使用Tensorflow搭建自编码器(Autoencoder)
自编码器是一种数据压缩算法,其中数据的压缩和解压缩函数是数据相关的.从样本中训练而来的.大部分自编码器中,压缩和解压缩的函数是通过神经网络实现的. 1. 使用卷积神经网络搭建自编码器 导入MNIST数 ...
- mac 安卓生成证书(uniapp项目安卓证书申请)
mac 安卓生成证书 义务需求: 最近在开发基于uniapp框架的app,到了打包发布的阶段,来尝试打包为安卓的apk安装包.在用HBuild打包的时候需要提供安卓的数字证书(.keystore 文 ...
- 关于C#调用非托管DLL,报“内存已损坏的”坑,坑,坑
因客户需求,与第三方对接,调用非托管DLL,之前正常对接的程序,却总是报“内存已损坏的异常”,程序进程直接死掉,折腾到这个点(2018-05-11 00:26),终于尘埃落定,直接上程序. 之前的程序 ...
- asp.net core 应用docke部署到centos7
前言 前期准备 win10 (不要安装hyper-V) VMware-Workstation-Pro/15.0 Xshell6 (非必需) VS2019 以上环境请自行安装 都是默认安装没什么可说的 ...
- Spark基础学习精髓——第一篇
Spark基础学习精髓 1 Spark与大数据 1.1 大数据基础 1.1.1 大数据特点 存储空间大 数据量大 计算量大 1.1.2 大数据开发通用步骤及其对应的技术 大数据采集->大数据预处 ...
- 前端路由、后端路由——想要学好vue-router 或者 node.js 必须得明白的两个概念
前端路由和后端路由的概念讲解 引言 正文 一.路由的概念 二.后端路由 三.前端路由 四.其他知识 结束语 引言 无论你是正在学习vue 还是在学习node, 你一定会碰到前端路由和后端路由这两个概念 ...
- python中a, b = a, a + b这条语句是如何执行的?
a,b=b,a+b,这条语句在"理解"上还是与C语言有些差别的.在Python中,可以做下面的方式理解:首先,把等号右边的算式分别算完再说,然后按照一一对应的关系把值赋给等号左边的 ...
- 用find命令删除某目录下及所有子目录中某类型的特定文件
当前目录下含有多级子目录,并且每一个子目录下都含有多个文件,如何删除当前目录及所有子目录下特定类型的文件,比如:*.pyc(所有文件名以“*.pyc”结尾的文件): 可以使用find命令,将当前目录下 ...
- 安国AU6989主控 + K9GBG08U0A(NAND) 制作4GB闪存驱动器
文档标识符:AU6989_FLASH-DRIVE_D-P8 作者:DLHC 最后修改日期:2020.8.22 本文链接: https://www.cnblogs.com/DLHC-TECH/p/AU6 ...


