lowbit 的定义

首先了解 lowbit 的定义

\(lowbit(n)\) ,为 \(n\) 的二进制原码中最低的一位 \(1\) 以及其后面的 \(0\) 所表示的数

举个简单的例子:

将 \(10\) 使用二进制表示为 \(1010\)

其中最低位的 \(1\) 为第2位(\(_{10}1_0\),从右往左数)

此时 \(lowbit(10)\) 使用二进制表示为 \(10\) ,即 \(2\) . (有关进制转换详见进制与进制转换


lowbit 的计算

如何计算 \(lowbit(n)\) 呢?

lowbit 的特殊情况

由于 lowbit 会将除最低位 \(1\) 以外所有的位 \(1\) 改为 \(0\) , lowbit 将只会对位 \(1\) 的位数高于1的二进制数产生影响,所以位 \(1\) 只有1位的二进制数和 \(0\) 处理后将得到原数据,即:

\[lowbit(2^n) = 2^n ~~~ ( n >= 0)
\]
\[lowbit(0) = 0
\]

方法一:递归

先暂时假定 \(n\) 为正整数

将 \(n\) 转换为二进制,可得: \(00...00x...xxx\) ( \(x\) 为 \(0\) 或 \(1\))

此时 \(n · 2\) 转换为二进制可得: \(00...00x...xxx · 10 ~=~ 00...0xx...xx0\)

假设 \(n\) 转为二进制后,末尾有 \(m\) 个连续位 \(0\) (显然,此时 \(lowbit(n) ~ = ~ 2^m\) )

因此, \(n · 2\) 转为二进制后,末尾有 \(m + 1\) 个连续位 \(0\) (此时 \(lowbit(n · 2) ~ = ~ 2^{m + 1} ~ = ~ 2^m · 2\) )

于是我们得到了:

\[lowbit(n · 2) = lowbit(n) · 2
\]

此时 \(n · 2\) 表示为二进制是 \(00...0xx...xx0\) ,怎么样,有没有什么想法?

将 \(n · 2\) 加上 \(1\) ,得: \(00...0xx...xx0 + 1 ~ = ~ 00...0xx...xx1\)

显然:

\[lowbit(2n + 1) ~ = ~ 1
\]

观察 \(n\) 的二进制形式: \(00...00x...xxx\)

对比 \(-n\) 的二进制形式:\(10...00x...xxx\) (在原码中,我们一般使用第一位存储符号, \(0\) 为正, \(1\) 为负)

很明显, \(lowbit(n) ~ = ~ lowbit(-n)\)

无论 \(n\) 的符号为负还是为正,奇偶性都一致,因此,我们在上面推导出来的公式对负整数仍然成立

综上所述,任意奇数的 lowbit 值都为 \(1\) ,任意偶数的 lowbit 值都为其乘 \(0.5\) 得到的值的 lowbit 值乘 \(2\)

通过这种思路,我们可以编写一个递归函数计算 \(n\) 的 lowbit 值,遇到奇数直接返回 \(1\) ,遇到偶数辄除以 \(2\) 后继续计算

写成伪代码是这样的:

int lowbit(int n) {
if (n % 2 == 1) return 1; // Odd
else return lowbit(n / 2) * 2; // Even
}

方法二:公式

在方法一中,我们使用了深度优先搜索,时间复杂度可能有点高,我们当然可以使用记忆化数组降低复杂度,但,我们是否可以推导出一个公式直接计算,将复杂度降低为 \(O(1)\) 呢?

当然是可行的。还是观察 \(n\) 的二进制形式: \(00...00x...xxx\) (假定 \(n\) 为非负整数)

还是对比 \(-n\) 的二进制形式:\(10...00x...xxx\)

如果对 \(10...00x...xxx\) 每一位取反(符号位除外),我们就得到了 \(-n\) 的反码: \(11...11(-x)...(-x)(-x)(-x)\)

此时 \(-n\) 末尾的 \(0\) 全部变为 \(1\) ,而最低位的 \(1\) 也难逃变为 \(0\) 的命运

如果我们现在将其加上 \(1\) ,我们将得到 \(-n\) 的补码: \(11...11(-x)...(-x)(-x)(-x) + 1\) ,反码末尾的 \(1\) 将重新变为 \(0\) ,而最低位 \(0\) 将重新变为 \(1\) ,其他位不变,仍然是取反的状态,此时如果将 \(-n\) 的补码与 \(n\) 原码进行按位与的运算( \(n\) 与 \(-n\) 的原码只有符号位的不同),由于除符号位、最低位 \(1\) 及其后面的位 \(0\) ,其他位都进行了取反,这些位将被赋值为 0 & 11 & 0,即 \(0\) ,而符号位也会赋值为 0 & 1 ,只剩最低位 \(1\) 及其后面的位 \(0\) 分别被赋值为 1 & 10 & 0 ,即 \(1\) 和 \(0\) ,最后结果为 \(lowbit(n)\) (或者说 \(lowbit(-n)\))

那么 \(n\) 的反码、补码呢?上文所述的只是负数的反码与补码的计算方式,实际上,正数的原码、反码、补码都是一样的(对于原码、反码、补码,本博文已经进行了必要的解释,但如果你对其感兴趣,想知道其详细解释,可参考这篇博文:二进制|原码、反码、补码

众所周知, C++ 中,数字是使用补码表示的,因此,我们可以将 \(n\) 的补码视为 \(n\) 的原码,在 C++ 中进行运算。于是,我们得到了\(n\) 的原码和 \(-n\) 的补码

上文中,我们提到了将 \(n\) 的原码和 \(-n\) 的补码进行按位与运算可以得到 \(lowbit(n)\) 和 \(lowbit(-n)\) ,现在我们使用 \(n\) 的补码作为其原码(毕竟是一样的),可以得到:

\[lowbit(n) = -n & n
\]

显然 \(n\) 是负数也不会造成影响

于是我们成功地得到了 lowbit 的计算公式,将算法的时间复杂度降低为 \(O(1)\) ,并简化了代码:

#define lowbit(n) (-n & n)

由于使用宏定义,一定要记得打括号,位运算的优先级是最低的


lowbit 的应用

lowbit 的应用也有很多,例如树状数组等,如果你对这方面感兴趣,不妨订阅一下我的博客,我以后会发布更多有趣且有用的算法知识

C++ 中的 lowbit的更多相关文章

  1. POJ 2309 BST(树状数组Lowbit)

    题意是给你一个满二叉树,给一个数字,求以这个数为根的树中最大值和最小值. 理解树状数组中的lowbit的用法. 说这个之前我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1全部清 ...

  2. 浅谈lowbit运算

    关于lowbit运算的相关知识 本篇随笔简单讲解一下计算机中位运算的一类重要运算方式--\(lowbit\)运算. lowbit的概念 我们知道,任何一个正整数都可以被表示成一个二进制数.如: \[ ...

  3. 【转载】区间信息的维护与查询(一)——二叉索引树(Fenwick树、树状数组)

    在网上找到一篇非常不错的树状数组的博客,拿来转载,原文地址. 树状数组 最新看了一下区间的查询与修改的知识,最主要看到的是树状数组(BIT),以前感觉好高大上的东西,其实也不过就这么简单而已. 我们有 ...

  4. C/C+小记

    1.struct与typedef struct struct Student{int a;int b}stu1; //定义名为Student的结构体,及一个Student变量stu1 struct { ...

  5. Python开源框架

    info:更多Django信息url:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC) ...

  6. (新人的第一篇博客)树状数组中lowbit(i)=i&(-i) 的简单文字证明

    第一次写博好激动o(≧v≦)o~~初一狗语无伦次还请多多指教   先了解树状数组http://blog.csdn.net/int64ago/article/details/7429868感觉这个前辈写 ...

  7. Educational Codeforces Round 18D(完全二叉树中序遍历&lowbit)

    题目链接:http://codeforces.com/contest/792/problem/D 题意:第一行输入n, q,分别表示给出一颗n个节点的中序遍历满二叉树,后面有q个询问; 接下来有q组形 ...

  8. pojBuy Tickets2828线段树或者树状数组(队列中倒序插队)

    这题开始的思路就是模拟:就像数组中插点一样,每一个想买票的人都想往前插队! 但是这样的话肯定TLE, 看了别人的思路之后才恍然大悟! 正解: 将开始的正序插入,变成倒序插入,这样的话,想一想:第 i ...

  9. #define lowbit(x) ((x)&(-x))原理详解

    #define lowbit(x) ((x)&(-x)) 也可以写成如下形式: int Lowbit(x) { return x&(-x); } 例如: 1> x = 1: 十进 ...

  10. C语言中的位运算的技巧

    一.位运算实例 1.用一个表达式,判断一个数X是否是2的N次方(2,4,8,16.....),不可用循环语句. X:2,4,8,16转化成二进制是10,100,1000,10000.如果减1则变成01 ...

随机推荐

  1. Netflow/IPFIX 流量收集与分析

    目录 文章目录 目录 Netflow(网络数据流检测协议) IPFIX(网络流量监测) IPFIX 组网架构 IPFIX 应用场景 Usage-based Accounting(基于使用流量的计费) ...

  2. 阿里云sdk调用

    slb调用 环境包安装 pip install alibabacloud_credentials  --trusted-host mirrors.aliyun.com  -i  http://mirr ...

  3. 大数据之Hadoop的HDFS存储优化—异构存储(冷热数据分离)

    异构存储主要解决,不同的数据,储存在不同类型的硬盘中,达到最佳性能的问题 1)存储类型 RAM_DISK:内存镜像文件系统 SSD:SSD固态硬盘 DISK:普通磁盘,在HDFS中,如果没有主动声明数 ...

  4. 【题解】A18747.眼红的同学

    题目链接:眼红的同学 题干信息很简单,看到数据量之后就不简单了.在数据量小的时候可以使用双层循环暴力的方法来求答案.显然对于这道题而言O(n^2)是完全过不去的. 前置知识: 使用树状数组求逆序对 会 ...

  5. openssl升级nginx升级支持openssl http2

    mkdir -p /usr/local/openssl #wget https://www.openssl.org/source/openssl-1.1.1d.tar.gz tar -xf opens ...

  6. itest(爱测试) 开源接口测试,敏捷测试管理平台10.0.0GA 发布

    一:itest work 简介 itest work 开源敏捷测试管理,包含极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试,接口Mock,还有压测 ,又有丰富的统计分析,8合1工作站.可按 ...

  7. kubernetes——资源管理

    Kubernetes 资源管理 介绍 kubernetes中,所有的内容都抽象为资源,用户需要通过操作资源来管理kubernetes. kubernetes的最小管理单元是pod而不是容器,所以只能将 ...

  8. react兄弟之间通信

    写入组件 import React, { Component } from 'react'//下面二个就是兄弟关系的组件 import Cmp1 from '../Child/Cmp1' import ...

  9. npm包管理器 切换npm源 使用nrm来管理npm镜像源

    npm是NodeJs项目模块管理工具,它已经集成了nodejs安装包中,在npm从从5.2版开始,增加了 npx 命令(解决的主要问题,就是调用项目内部安装的模块,很多工具不再需要npm -g 全局来 ...

  10. kettle从入门到精通 第二十七课 邮件发送

    1.我们平常在做数据同步的时候,担心转换或者job没有正常运行,需要加上监控机制,这个时候就会用到邮件功能. 下图是一个简单的测试邮件发送功能的转换.在kettle.properties文件中设置邮件 ...