树状数组课件——Skywalker
作者:互联网
树状数组(Binary Indexed Tree)
目录Author: 朱胜豪
Creation time:2021/8/10 21:28
Last update:2021/8/12 21:10
情景引入
在处理一段区间的和的时候,我们可以通过前缀和算法来提高运算效率,除去预处理,能做到每次询问$o(1)$算出,这个时间复杂度是极其优秀的,但是如果修改一个点后,再次询问一段区间的和,我们发现,最坏情况下,我们要更新$n$次数组,我们不能接受。那么,如何快速的询问一段区间的长度,并且修改的时候更新速度也要足够优秀,这时候,就体现出了树状数组算法的优越性。
即:能以$o(logn)$的时间复杂度进行单点修改,以$o(logn)$的时间复杂度进行区间查询。
$log$级别的算法是极其可靠的。$2^{31} log$后也不过是$31$而已(这边的$log$是以$2$为底)。
前置知识
顾名思义,树状数组是根据树形结构来优化运算,从而达到可观的时间复杂度,我们需要一些工具来进行遍历树的每个节点,来方便我们的运算。
lowbit函数的定义:lowbit函数的作用是返回二进制下最后一位$1$代表的十进制数,例如:$12(1100)$,那么他的最后一位$1$所代表的十进制数就是 $4(100)$。
lowbit函数的实现:
int lowbit(int n)
{
return n & (-n);
}
lowbit函数的证明:
$$
设x的最低位的1在第k位,那么0~k - 1位都是0\
-x = (\sim x + 1)取反加一,取反后0 \sim k -1都会变成1,+1后,则0 \sim k-1又消成了0\
第k位变成1,前面都是原来的取反的结果,则为一个零,一个一,取与后为0\
得证\
$$
lowbit只是一个工具,证明有兴趣可以考虑,无兴趣可看可不看。
树状数组
铺垫了一点点,下面来到我们的树状数组,下面将从:树状数组的理论基础,树状数组的代码实现,树状数组的用途来进行叙述。
树状数组的理论基础:
树状数组 = 前缀和 + 二进制拆分
- 每个内部节点$c[x]$保存以它为根的子树的所有节点和。
- 每个内部节点$c[x]$保存的节点个数等于lowbit(x)的位数。
- 除树根外,每个内部节点$c[x]$的父节点是c[x + lowbit(x)]。
- 树的深度为$O(log(n))$
以上性质来自算法进阶指南.
$$
c[x]保存的是序列a的区间[x - lowbit + 1 , x]的和\
即:\sum_{i = x - lowbit(x) - 1}^xa[i]
$$
树状数组的代码实现:
树状数组支持的基本操作是查询前缀和,即序列第1~x个数的和,[1 , x]已经被划分成了$log(N)$个小区间,而每个区间,都在$c$数组中,所以查询区间前缀和的代码如下,其时间复杂度为$O(logN)$.
- 查询操作,查询a[1 ~ x]的和
int query(int x)
{
int ans = 0;
for(int i = x;i >= 1;i -= lowbit(i)) ans += c[i];
return ans;
}
当然,如果查询$l \sim r$的前缀和,我们可以利用前缀和思想,$query(r) - query(l - 1)$
树状数组支持的第二个操作是单点增加,在$x$位置加上value后,树状数组只需要$log(N)$的时间复杂度,维护树状结构和其性质。
- 单点插入 , 在x位置插入一个值v
void add(int x , int v)
{
for(int i = x;i <= n;i += lowbit(i)) c[i] += v;
}
至此,树状数组的两大基础操作,已经全部概述完毕,下面是树状数组的应用。
树状数组的应用:
- 因为其独特的结构,用于存储,修改,和查询,前缀和能有很好的效果
- 求逆序对
若$i < j$,且$a[i] > a[j]$,则称$a[i]$与$a[j]$构成逆序对,我们已知的逆序对求法较为优秀的算法是归并排序,然而,树状数组也可以做到。
可以正序求出逆序对,也可以逆序遍历求出逆序对,这边给出的是逆序求逆序对的方法。
- 在序列$a$的数值范围上建立树状数组,对于每个$a[i]$,累加到答案$ans$中。
- 执行单点增加操作,把位置$a[i]$上出现的数加上1,相当于($tr[a[i]] ++$)。
for(int i = n; i ; i --)
{
ans += query(a[i] - 1);
add(a[i] , 1);
}
- ans即为所求
树状数组的拓展应用:
利用差分知识,我们可以维护区间修改,单点查询,篇幅有限,这里不展开了。
练习题目
题目集:https://vjudge.ppsucxtt.cn/contest/452921#overview
题目集密码:HPUACM
题目名称 | 考察知识点 |
---|---|
Sort it(例题1) | 树状数组求逆序对 |
Inversion | 离散化,树状数组求逆序对 |
敌兵布阵(例题2) | 树状数组模版题 |
Japan | 树状数组求逆序对 |
Matrix | 二维树状数组 |
A Simple Problem with Integers | 树状数组+差分+推公式 |
Stars | 树状数组求逆序对 |
Mobile phones | 二维树状数组 |
Bubble Sort | 树状数组,思维 |
空间大师 | 树状数组,二分 |
例题1代码:
#include <bits/stdc++.h>
#define lowbit(x) (x & (-x))
using namespace std;
typedef long long LL;
const int N = 1000;
int n;
int a[N + 10];
LL tr[N + 10];
LL query(int x)
{
LL ans = 0;
for(int i = x;i >= 1;i -= lowbit(i)) ans += tr[i];
return ans;
}
void add(int x , int v)
{
for(int i = x;i <= n;i += lowbit(i)) tr[i] += v;
}
int main()
{
while(~scanf("%d" , &n))
{
LL ans = 0;
memset(tr , 0 , sizeof tr);
for(int i = 1;i <= n;i ++) scanf("%d" , &a[i]);
for(int i = n; i ;i --)
{
ans += query(a[i] - 1);
add(a[i] , 1);
}
printf("%lld\n" , ans);
}
return 0;
}
例题2代码
#include <bits/stdc++.h>
#define lowbit(x) (x & (-x))
using namespace std;
typedef long long LL;
const int N = 50010;
int n , t;
int a[N];
LL tr[N + 10];
LL query(int x)
{
LL ans = 0;
for(int i = x;i >= 1;i -= lowbit(i)) ans += tr[i];
return ans;
}
void add(int x , int v)
{
for(int i = x;i < N;i += lowbit(i)) tr[i] += v;
}
int main()
{
scanf("%d" , &t);
for(int ii = 1;ii <= t;ii ++)
{
memset(tr , 0 , sizeof tr);
scanf("%d" , &n);
for(int i = 1;i <= n;i ++)
{
scanf("%d" , &a[i]);
add(i , a[i]);
}
printf("Case %d:\n" , ii);
int x , y;
string op;
while(cin >> op)
{
if(op == "End") break;
scanf("%d %d" , &x, &y);
if(op == "Query") printf("%lld\n" , query(y) - query(x - 1));
else if(op == "Sub") add(x , -y);
else if(op == "Add") add(x , y);
}
}
return 0;
}
参考资料
资料:
算法进阶指南——李煜东
没参考,看着写的不错,推荐下
视频(墙裂推荐):
【18级李强会长b站视频】
标签:树状,int,lowbit,Skywalker,课件,数组,ans,逆序 来源: https://www.cnblogs.com/walkonthesky/p/15143073.html