树状数组学习笔记
概念
树状数组,又称二叉索引树,是一种代码简单、应用广泛、unbelievable的数据结构
它大概长这样:
实现
观察上图,显然:
不难发现(其中 \(k\) 为 \(i\) 的二进制中从最低位到高位连续零的长度):
(其实 \(c_i\) 就是一个特殊的前缀和数组
我们有了 \(c_i\) 的通项公式后,还会发现一个问题:如何求出每个 \(i\) 所对应的 \(k\)
Answer:找 \(i\) 最低位的 \(1\) 即可
我们引入一个求某个数最低位的函数: lowbit(x)
inline int lowbit(int x) {
return x & (~x + 1);
}
(原理可自行感性推导
单点修改
由 \(c_i\) 的通项公式,我们可以推出每个包含 \(a_i\) 的 \(c_j\) 为 \(c_{i+2^k},c_{(i+2^k) +2^k},...\)
其中 \(2^k\) 为 lowbit(i)
写成代码,就是这样:
inline void update(int x, int k) {
for (; x <= n; x += lowbit(x))
c[x] += k;
}
区间查询
由 \(c_i\) 的通项公式,我们同样可以推出 \(a_i\) 的前缀和为 \(c_i + c_{i-2^{k1}},c_{(i-2^{k1})-2_{k2}},...\)
写成代码,就是这样:
inline int query(int x) {
int res = 0;
for (; x; x -= lowbit(x))
res += c[x];
return res;
}
这样区间 \([l,r]\) 的和就是 query(r)-query(l-1)
P3374 【模板】树状数组 1
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 7;
int a[N], c[N];
int n, m;
inline int lowbit(int x) {
return x & (~x + 1);
}
inline void update(int x, int k) {
for (; x <= n; x += lowbit(x))
c[x] += k;
}
inline int query(int x) {
int res = 0;
for (; x; x -= lowbit(x))
res += c[x];
return res;
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%d", a + i);
update(i, a[i]);
}
for (int opt, x, k, l, r; m; --m) {
scanf("%d", &opt);
if (opt == 1) {
scanf("%d%d", &x, &k);
update(x, k);
}
else {
scanf("%d%d", &l, &r);
printf("%d\n", query(r) - query(l - 1));
}
}
return 0;
}
扩展:O(n) 建树
由于每一个节点的值是所有与自己直接相连的儿子的值的,因此可以倒着考虑贡献,即每次确定完儿子的值后,用自己的值更新自己的直接父亲
inline void init() {
for (int i = 1; i <= n; ++i) {
c[i] += a[i];
if(i + lowbit(i) <= n)
c[i + lowbit(i)] += c[i];
}
}
扩展:差分树状数组
前置芝士:差分
差分树状数组,即将普通树状数组中的前缀和数组改为差分数组
区间修改
由差分区间修改的性质,我们容易写出代码
inline void update(int l,int r,int val) {
for(;l<=n;l+=lowbit(l))
c[l]+=val;
for(++r;r<=n;r+=lowbit(r))
c[r]-=val;
}
单点查询
由差分单点查询的性质,我们容易写出代码
inline int query(int pos) {
int res = 0;
for (; pos; pos -= lowbit(pos))
res += c[pos];
return res;
}
P3368 【模板】树状数组 2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 7;
int c[N];
int n, m;
inline int lowbit(int x) {
return x & (~x + 1);
}
inline void update(int pos, int val) {
for (; pos <= n; pos += lowbit(pos))
c[pos] += val;
}
inline int query(int pos) {
int res = 0;
for (; pos; pos -= lowbit(pos))
res += c[pos];
return res;
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1, val; i <= n; ++i) {
scanf("%d", &val);
update(i, val);
update(i + 1, -val);
}
for (int opt, pos, val, l, r; m; --m) {
scanf("%d", &opt);
if (opt == 1) {
scanf("%d%d%d", &l, &r, &val);
update(l, val);
update(r + 1, -val);
}
else {
scanf("%d", &pos);
printf("%d\n", query(pos));
}
}
return 0;
}
应用
P4868 Preprefix sum
Solution:求:
Description:化简:
对于 $\sum_{i=1}^k a_i $ 与 $ \sum_{i=1}^k i \times a_i$ ,我们可以用树状数组维护
Code:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5 + 7;
ll a1[N], a2[N], c1[N], c2[N];
int n, m;
inline int lowbit(int x) {
return x & (~x + 1);
}
inline void update1(int x, ll y) {
for (; x <= n; x += lowbit(x))
c1[x] += y;
}
inline void update2(int x, ll y) {
for (; x <= n; x += lowbit(x))
c2[x] += y;
}
inline ll query1(int x) {
ll res = 0;
for (; x; x -= lowbit(x))
res += c1[x];
return res;
}
inline ll query2(int x) {
ll res = 0;
for (; x; x -= lowbit(x))
res += c2[x];
return res;
}
signed main() {
ios::sync_with_stdio(0),
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a1[i];
update1(i, a1[i]);
a2[i] = i * a1[i];
update2(i, a2[i]);
}
for (string str; m; --m) {
cin >> str;
if (str == "Query") {
int x;
cin >> x;
cout << (x + 1)*query1(x) - query2(x) << '\n';
}
else {
ll x, y;
cin >> x >> y;
update1(x, y - a1[x]), a1[x] = y;
update2(x, x * y - a2[x]), a2[x] = x * y;
}
}
return 0;
}
P1908 逆序对
我们将原数列离散化,然后将每个数一次插入树状数组中,每次插入时记录一下前面有多少个比它大的数即可
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 5e5 + 7;
int a[N], b[N], c[N];
ll ans;
int n, m;
inline int lowbit(int x) {
return x & (~x + 1);
}
inline void update(int x, int k) {
for (; x <= n; x += lowbit(x))
c[x] += k;
}
inline int query(int x) {
int res = 0;
for (; x; x -= lowbit(x))
res += c[x];
return res;
}
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", a + i);
b[i] = a[i];
}
sort(b + 1, b + 1 + n);
int cnt = unique(b + 1, b + 1 + n) - b - 1;
for (int i = 1; i <= n; ++i)
a[i] = lower_bound(b + 1, b + 1 + cnt, a[i]) - b; // 离散化
for (int i = 1; i <= n ; ++i) {
update(a[i], 1);
ans += i - query(a[i]);
}
printf("%lld", ans);
return 0;
}
原文地址:https://www.cnblogs.com/wshcl/p/TreeArray.html
- 微信公众平台开发接口PHP SDK完整版
- 我的HTML总结之HTML发展史
- BZOJ 4152: [AMPPZ2014]The Captain(最短路)
- js获取div编辑框,textarea,input text的光标位置,兼容FF和IE
- BZOJ 4289: PA2012 Tax(最短路)
- php QR Code二维码生成类
- BZOJ 3714: [PA2014]Kuglarz(最小生成树)
- 我的HTML总结之表单
- php 二维码生成类
- HDU 2516 取石子游戏(斐波那契博弈)
- angularjs MVC、模块化、依赖注入详解
- BZOJ 2940: [Poi2000]条纹(Multi-Nim)
- PHP页面跳转代码
- angularjs 控制器、作用域、广播详解
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Ajax设置请求和接收响应、自己封装简易jQuery.Ajax、回调函数
- 回调、使用Promise封装ajax()、Promise入门
- 模块化、闭包与立即执行函数的使用、MVC里的V和C
- 使用leancloud给简历加数据库,实现留言功能
- MVC中的M(model)、MVC总结
- JS面向对象一:MVC的面向对象封装
- JS面向对象二:this/原型链/new原理
- JS题目总结:原型链/new/json/MVC/Promise
- 异步与回调/函数的作用域链
- 前端工程化
- 使用NPM
- Cookie
- 虚拟DOM
- HTTP缓存(Cache-Control、Expires 、ETag)
- 以登录注册理解Cookie的作用过程