惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Schneier on Security
Schneier on Security
N
Netflix TechBlog - Medium
IT之家
IT之家
MongoDB | Blog
MongoDB | Blog
博客园_首页
S
SegmentFault 最新的问题
H
Help Net Security
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
T
The Blog of Author Tim Ferriss
量子位
GbyAI
GbyAI
M
MIT News - Artificial intelligence
Recorded Future
Recorded Future
P
Privacy & Cybersecurity Law Blog
B
Blog
月光博客
月光博客
博客园 - 聂微东
Vercel News
Vercel News
罗磊的独立博客
腾讯CDC
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
Arctic Wolf
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
L
Lohrmann on Cybersecurity
I
Intezer
小众软件
小众软件
T
The Exploit Database - CXSecurity.com
Jina AI
Jina AI
C
Check Point Blog
AWS News Blog
AWS News Blog
C
Cisco Blogs
Martin Fowler
Martin Fowler
The Last Watchdog
The Last Watchdog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
宝玉的分享
宝玉的分享
S
Security Affairs
大猫的无限游戏
大猫的无限游戏
N
News and Events Feed by Topic
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
H
Hacker News: Front Page
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
P
Proofpoint News Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog

Sehnsucht

记一次博客换图床的过程 观《花束般的恋爱》 读《活过》 东京 旅行篇 亲人逝去 我为什么想养鱼 防止AI爬取你的博客 「monthly 」博客重构 游戏全成就 好看的小说 人生的每一步都不会是浪费-2024年终 「weekly」星期五综合征 「weekly」听播客 杂谈 读《美丽新世界》 「weekly」社交媒体 恶性事件 新世界 「weekly」认知觉醒 美丽新世界 follow 「weekly」独立博客9问题 双十一买书 读《惊呆了!原来这就是社会学》 一次聊天与自我建设 2024-09月记 补番《relife》 lvm简单使用 博客自动发布方案 某公众号废案 fail2ban基本使用 Podman 环境使用 Nginx Proxy Manager 最佳实践 2024-07同学聚会 vps使用podman部署freshrss Gitlab CICD 实践,思考与记录 装备升级:新的PC gitlab局域网搭建流程 FastMail迁移至GMail+Cloudflare Email Routing 读《黑客与画家》 leetcode 第 391 场周赛 Astro添加过渡后DarkModeToggle按钮失效 nuxt3使用echarts5渲染中国地图 人生刻度 又到凤凰花朵开放的时候 2023-05-10 实习结束 2023-04-26 广州博物馆游记 YOASOBI「たぶん」官方音乐视频 风灵玉秀-发如雪 带我的老哥离职了 从p10k转向Starship lunarvim国内安装踩坑记录 什么是对称加密非对称加密、密钥交换、数字签名、证书 强调体验的一种思考时间的方式 ArchLinux使用vscode编写latex报错The font "FontAwesome" cannot be found ArchLinux安装(移动硬盘)流程 yay一个或多个文件没有通过有效性检查! 2022年终 hugo建站伊始 clash设置relay前置代理(校园网破解改进记录) 群青 · YOASOBI · Ayase 华为 matepad pro 11使用体验 Nginx使用acme.sh免费安装ssl证书 永远属于你的安娜 在线 童年趣事 童心,是比野心更难得的梦想 2021年终总结 基于jeeSite的软件测试课程作业 流程记录 Linux不同用户安装不同版本jdk Wifi4更换Wifi6路由器的使用体验 Flask 上传图片并灰度显示 玩客云刷机debain个人记录 jython 简单入门 Jenkins 个人搭建流程记录 集成邮件系统(qq邮箱),gitlab服务器,freestyle风格 在Web项目中配置Log4j --指南-- Codeforces Round 753 (Div. 3) ABCDE 浅谈fork函数 JavaWeb servlet 使用Cookie记录用户访问次数 Codeforces Round Educational Codeforces Round 114 (Rated for Div. 2) ABC 2021牛客多校7 I xay loves or 2021牛客多校9 H Happy Number 2021牛客多校8 D OR 2021牛客多校5 B Boxes 2021牛客多校5 H Holding Two 2021牛客多校2 D Er Ba Game 2021牛客多校1 F Find 3-friendly Integers 2021牛客多校1 B Ball Dropping 记人生第一次投简历和笔试 原型模式 2021-2-20 雨时随记 关于一维差分数组的例子 halo个人建站 关于手机ping电脑和电脑ping手机 泛型类简单理解 CodeForces - 849C From Y to Y 2020牛客国庆集训派对day3 Leftbest 约瑟夫环问题---2020牛客国庆集训派对day2 AKU NEGARAKU mysql 5.7 忘记密码如何更改 CF 1099B Squares and Segments CF 998A Balloons CF 998B Cutting 链表应用之多项式相加 CF 1150A Stock Arbitraging CF 1199A City Day CF 1199B Water Lily CF 982A Row CF 934A A Compatible Pair CF 629B Far Relative’s Problem 651A Joysticks 651B Beautiful Paintings
树状数组简单理解
2024-08-12 · via Sehnsucht

引入

场景

给出一个长度为 n 的数组,完成以下两种操作

  • 将第 x 个数加上 k
  • 输出区间[x,y]内每个数的和

最简单的方式自然是前缀和,但暂时不讨论它,我们引入新的算法---树状数组

下面以短数组举例

|1|2|1|5|2|5|2|5|

每次单次累加就很慢了,所以我们可以将相邻的数字累加,形成上一层的数字,再继续累加形成上一层,如下所示

|                       23                      |
|         **9**         |           14          |
|     3     |     6     |   **7**   |     7     |
|  1  |  2  |  1  |  5  |  2  |  5  |**2**|  5  |

|  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  | # 下标

如果是求下标7与之前的数字的和,直接累加9+7+2即可

排除

在这样的数组结构中:

|                       23                      |
|           9           |         **14**        |
|     3     |   **6**   |     7     |   **7**   |
|  1  |**2**|  1  |**5**|  2  |**5**|  2  |**5**|

不少数字其实是用不到的,例如:上面有两个数字的和的时候,右边的数字可以用上面的数字减去左边的数字获得

所以最后可以得到这样的数组:

|1|3|1|9|2|7|2|23|

这个数组就是树状数组,树状数组和原始数组刚好一样长

得到这个树状数组,我们如何去做增加删除呢,如果一层一层加上去不是更麻烦吗,这就需要关键函数lowbit

lowBit

x & (-x)

这就是lowbit函数

表示非负整数x在二进制表示下最低位以及其后面的0构成的数值

例如:

  • lowbit(6),也就是lowbit(110),它的值就是二进制10 ,也就是2
  • lowbit(44),也就是lowbit(101100),它的值就是二进制100,也就是4

原理

简单解释一下lowbit的x & (-x)究竟发生了什么

在计算机底层中,负数是以补码的形式存储的,补码就是对它每个位数取反1再加1

例如我们现在求-12在计算机中存储的真实的值(就是求12的补码),12的二进制是00001100,取反就是11110011,再加1就是11110100,所以这个11110100就是-12在计算中中存储的真实的值

00001100 # 12
11110011 # 每一位取反,反码
11110100 # 反码+1,补码

至于为什么要+1,因为二进制只能存储偶数个数字,而除了正数和负数之外还有0,所以负数就匀了1个空间出来给0,所以整数类型正数负数范围通常差1。例如c++的int范围是-2^152^15 - 1

回到正题,x & (-x)究竟发生了什么

我们把12和-12的值放在一起看看

00001100 # 12
11110100 # -12
00000100 # 12 & -12

我们可以得到

这样的操作会得到,整数x的,最低位1以及后面的0所构成的数值

应用

层级规律

依旧用上面的例子距离:

|                       23                      |
|           9           |           14          |
|     3     |     6     |     7     |     7     |
|  1  |  2  |  1  |  5  |  2  |  5  |  2  |  5  |

成为树状数组后,将他们的下标列出:

	4   |                       9                       |
	3   |           4           |           8           |
	2   |     2     |     X     |     6     |     X     |
    1   |  1  |  X  |  3  |  X  |  5  |  X  |  7  |  x  |

他们的二进制:

	4   |                       1000                       |
	3   |           100          |             X           |
	2   |     10     |     X     |     110     |     X     |
    1   |  1  |  X  |  11  |  X  | 101 |  X  |  111  |  x  |

可以看到我上面的数组,在旁边我给树状数组加了一个层级,这个层级实际上就是下标的lowbit的长度

即:1层的lowbit都是1,2层的lowbit都是10,3层的lowbit都是100…

通过上面的规律我们可以衍生出后文的两个子规律

index + lowbit(index)

换一张图表:

                            s8
               s4              |
        s2      |       s6      |
        |       |       |       | 
    s1  |   s3  |   s5  |   s7  |
    |   |   |   |   |   |   |   |
    a1  a2  a3  a4  a5  a6  a7  a8

我们现在希望构造树状数组,从a数组转换成s数组,这时候就需要index + lowbit(index)(一直往上累加)

沿着链一直往上走,把a1放入s1,s2,s4,s8,把a2放入s2,s4,s8…把a7放入s8

这个下标是如何生成的呢?

下一个下标就是index+lowbit(index)

1的lowbit就是2,那么下一个节点就是2,2的lowbit是2,下一个节点就是4,4的lowbit就是4,下一个节点就是8,故得到s1,s2,s4,s8

这是一个非常巧合的东西,index+lowbit(index)意味着,index的二进制中的最低位1,要进位了,所以会导致上移一层

index - lowbit(index)

如果我们希望下标1到7的前缀和,用树状数组怎么求?

7的二进制是111,按照下图我们应该累加s7,s6,s4

                              s8
                s4              |
        s2      |       s6      |
        |       |       |       | 
    s1  |   s3  |   s5  |   s7  |
    |   |   |   |   |   |   |   |
    a1  a2  a3  a4  a5  a6  a7  a8

s7,s6,s4的二进制是111110100 ,也就是说,累加的上一个下标可以通过index - lowbit(index)来得到

7的下标是111,lowbit是1,那么减去lowbit就是110,也就是下标6二进制是110110的lowbit是10110减去10就是100也就是4

模板

int lowbit(int x) {
	return x & (-x);
}

// 单点增加数据,在x下标的原数组上增加k
// 其中x是以1开头的下标,x += lowbit(x)就是移动到下一层
void add(int x, int k) {
	for (; x <= n; x += lowbit(x) ) arr[x] += k;
}

// 前缀和查询
long long sum(int x) {
    long long ans = 0; // 开long long,省的爆int
    for (int i = x;i;i -= lowbit (i)) ans += c[i];
    return ans;
}
  1. 对每个位数取反也叫反码 ↩