C++ 中的 lowbit
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 & 1或1 & 0,即 \(0\) ,而符号位也会赋值为0 & 1,只剩最低位 \(1\) 及其后面的位 \(0\) 分别被赋值为1 & 1和0 & 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的更多相关文章
- POJ 2309 BST(树状数组Lowbit)
题意是给你一个满二叉树,给一个数字,求以这个数为根的树中最大值和最小值. 理解树状数组中的lowbit的用法. 说这个之前我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1全部清 ...
- 浅谈lowbit运算
关于lowbit运算的相关知识 本篇随笔简单讲解一下计算机中位运算的一类重要运算方式--\(lowbit\)运算. lowbit的概念 我们知道,任何一个正整数都可以被表示成一个二进制数.如: \[ ...
- 【转载】区间信息的维护与查询(一)——二叉索引树(Fenwick树、树状数组)
在网上找到一篇非常不错的树状数组的博客,拿来转载,原文地址. 树状数组 最新看了一下区间的查询与修改的知识,最主要看到的是树状数组(BIT),以前感觉好高大上的东西,其实也不过就这么简单而已. 我们有 ...
- C/C+小记
1.struct与typedef struct struct Student{int a;int b}stu1; //定义名为Student的结构体,及一个Student变量stu1 struct { ...
- Python开源框架
info:更多Django信息url:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC) ...
- (新人的第一篇博客)树状数组中lowbit(i)=i&(-i) 的简单文字证明
第一次写博好激动o(≧v≦)o~~初一狗语无伦次还请多多指教 先了解树状数组http://blog.csdn.net/int64ago/article/details/7429868感觉这个前辈写 ...
- Educational Codeforces Round 18D(完全二叉树中序遍历&lowbit)
题目链接:http://codeforces.com/contest/792/problem/D 题意:第一行输入n, q,分别表示给出一颗n个节点的中序遍历满二叉树,后面有q个询问; 接下来有q组形 ...
- pojBuy Tickets2828线段树或者树状数组(队列中倒序插队)
这题开始的思路就是模拟:就像数组中插点一样,每一个想买票的人都想往前插队! 但是这样的话肯定TLE, 看了别人的思路之后才恍然大悟! 正解: 将开始的正序插入,变成倒序插入,这样的话,想一想:第 i ...
- #define lowbit(x) ((x)&(-x))原理详解
#define lowbit(x) ((x)&(-x)) 也可以写成如下形式: int Lowbit(x) { return x&(-x); } 例如: 1> x = 1: 十进 ...
- C语言中的位运算的技巧
一.位运算实例 1.用一个表达式,判断一个数X是否是2的N次方(2,4,8,16.....),不可用循环语句. X:2,4,8,16转化成二进制是10,100,1000,10000.如果减1则变成01 ...
随机推荐
- Vue+springboot集成PageOffice实现在线编辑Word、excel文档
说明: PageOffice是一款在线的office编辑软件,帮助Web应用系统或Web网站实现用户在线编辑Word.Excel.PowerPoint文档.可以完美实现在线公文流转,领导批阅,盖章.可 ...
- Asp-Net-Core开发笔记:使用原生的接口限流功能
前言 之前介绍过使用 AspNetCoreRateLimit 组件来实现接口限流 从 .Net7 开始,AspNetCore 开始内置限流组件,当时我们的项目还在 .Net6 所以只能用第三方的 现在 ...
- 深入理解 Swoole 的底层加载原理
首发原文链接:深入理解 Swoole 的底层加载原理 PHP 扩展加载 我们从 php-src/sapi/cli/php_cli.c:1159 文件的入口函数 int main(int argc, c ...
- PHP 网络通信底层原理分析
一.引言 我们日常的程序开发大多数都是以业务为主,很少会接触到底层逻辑.对于我们程序员来说,了解程序的底层运行逻辑,更有助于提升我们对程序的理解.我相信大多数的人,每天基本上都是完成业务需求.当然,完 ...
- C语言-使用malloc导致的奔溃问题
在使用malloc.memset.free的过程中,出现了程序奔溃,大致现象如下. 程序的实现大致如下: #include <stdio.h> #include <stdlib.h& ...
- 代码审计——基础(JAVAWEB)
JAVAWEB 目录 JAVAWEB Servlet技术 JavaWeb概述 Servelt与Servlet容器 Servlet概念 Tomcat Web程序结构 Servlet容器响应客户请求的过程 ...
- Linux扩展篇-shell编程(五)-流程控制(一)-if语句
基本语法: (1)单分支 if [ condition ];then statement(s) fi 或 if [ condition ] then statement(s) fi (2)多分支 if ...
- ABC351
我多久没更新这个系列了啊 E 把格子分成两类,每一类之间的坐标均可互相走到. 然后将这里面的点都旋转 \(45\) 度,于是这个问题就被转换成曼哈顿距离的问题了. 我们可以把 \(x\) 和 \(y\ ...
- mysql windows 下配置可远程连接
1.在防火墙入站规则里加入 3306 端口,3306 为你安装mysql 时的端口. 2.在mysql 命令行中输入: #应用mysql数据库use mysql;#将root用户可访问改成所有upd ...
- Javascript高级程序设计第七章 | ch7 | 阅读笔记
迭代器与生成器 在软件开发领域,"迭代"的意思是按照顺序反复多次执行一段程序 理解迭代 在JavaScript中,计数循环就是最简单的迭代 但是这种迭代有点问题: 1. 迭代之前需 ...