谈谈树状数组

fenwick tree

树状数组已经是时代的眼泪了
感觉随着各种版本的线段树出世, 连区间和时间上都跟树状数组差不多了, 而且就我个人而言, 线段树比树状数组更容易理解一些
但是毕竟树状数组码量要小, 简单也是优势

复杂度

可差分信息, 比如区间和, 是可以logn维护的, 哪怕是区间加和, 也可以用两个数组来维护
而不可差分信息, 比如区间max值, 只能(logn)^2来维护, 所幸(logn)^2并不大

不同的写法

有太多种不同的写法了
很多人写fenwick tree是拿lowbit写, 下标从1开始
然而我抄来的板子是从0开始的, 我也比较喜欢从0开始的数组

template <typename T>
class fenwick
{
public:
vector<T> fenw;
int n;
fenwick(int _n) : n(_n)
{
fenw.resize(n);
}
void modify(int x, T v)
{
while (x < n)
{
fenw[x] += v;
x |= (x + 1);
}
}
void get(int x)
{
T v{};
while (x >= 0)
{
v += fenw[x];
x = (x & (x + 1)) - 1;
}
return v;
}
};

如何理解树状数组

lowbit当然好理解, 每个vec[i]维护的是从i开始往前的lowbit位的数据
每次add的时候, 需要通过+lowbit找到自己的所有父(这里用类似线段树的方式表达了一下同样需要保存这份数据的区间)加上数据
而query(x)的时候, vec[x]保存了lowbit(x)个元素, x-lowbit指的是剩余需要求的元素数量, 来到vec[x-lowbit]继续求

x|(x+1)其实是什么呢?
放几张图
第一张是lowbit函数作用在[1, 1025)上的函数图像
image
第二张是x|(x+1)作用在[0, 2048)上的函数图像
image

能看出什么规律来吗?
有点类似是吧
然后我要放一张(x|(x+1))-x作用在[0, 2048) 的图像
image

do you get it?
如果你把他们的值打印出来的话
你会发现, 他们在区间上每一位所管辖的位数就是一样的.
为什么呢? 还记得lowbit是如何跳区间的吗?

void add(int x, int z)
{
while (x <= n)
{
c[x] += z;
x += lowbit(x);
}
}

而第二种方法是如何跳区间的?

void modify(int x, T v)
{
while (x < n)
{
fenw[x] += v;
x |= (x + 1);
}
}

一切尽在不言中, 如果再说仔细一些的话, 这之间的区别只是位运算的一些技巧区别

arr1 = np.arange(0, 1000)
arr2 = np.arange(1, 1001)
def op4(x):
return (x|(x+1))+1
np.vectorize(op4)(arr1)
def op5(x):
return x+(x&(-x))
np.vectorize(op5)(arr2)

这俩矩阵的输出是一模一样的
另一个当然也是
image

用法

  • log(n)
    在需要区间加和的时候, 一般是使用差分数组的思维, 只修改前后两个值, 这是2log(n)的复杂度
    那查询呢?
    image
    维护两个数组即可

  • (log(n))^2
    计算不可差分值的时候, 也就是(x&(x+1))-1跳到l左边的话, 就别跳了, 改成x-1, 这个区间合并的过程是logn的, 所以整体复杂度是(logn)^2

posted on   tianlonghuangwu  阅读(8)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示