浅谈用于区间求和的数据结构思路碎片
前言
本文将涉及三种基础数据结构:
1.树状数组
2.线段树
3.分块
本文没有太大的逻辑联系,只是将一些思路碎片讲了讲。
注意:本文默认读者了解三个结构的基本实现!!!
树状数组
维护范围:满足区间可加性,并有逆运算的信息。
例如:和,异或和。
码长:较短。
效率:很高,
线段树
维护范围:满足区间可加性的信息。
例如:和,异或和,或和,最大子段和。
码长:较长。
效率:较高,巨大常数
分块
维护范围:???。(本身就是优雅的暴力好不好?)
例如:和,异或和,或和,最大子段和,区间众数。
码长:较长。
效率:一般,超大常数
从分块说起
其实很早之前我就想过,既然对每个块的暴力加lazy,和边角的暴力加值都像是原来的暴力,那可不可以对每个块和块内的元素进行分块呢?
这个想法一直持续到我看到一片文章中这么说:
本人亲自配图
这个应该很好理解吧?
此时,我的想法带进去就是增加这棵树的深度,以减少每个节点中的子节点数。
其实五层的话在
先暂时抛开什么区间可加性,来研究一下珂学,不,是研究究竟怎么分最好。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n=1e6;
double l=1.001,r=1000,mid1,mid2;
double f(double x){
return x*log(n)/log(x);
}
int main(){
for(int i=1;i<=1000;i++){
mid1=l+(r-l)/3;
mid2=l+(r-l)*2/3;
if(f(mid1)-f(mid2)>1e-9)l=mid1;
else r=mid2;
}
printf("ans=%.3f\nf(2)=%.3f\nf(3)=%.3f\nf(ans)=%.3f\n",l,f(2),f(3),f(l));
return 0;
}
ans=2.718
f(2)=39.863
f(3)=37.726
f(ans)=37.554
树状数组是线段树不可替代的
相信大家都知道权值线段树吧?其实树状数组也有类似用法。
权值线段树可以用于查询数的排名,但用排名查找数则有些艰难,不论二分还是倍增都是
但树状数组就不同了,它被称为
可以设计一个这样的算法:
1.初始化两个变量ans=0和sum=0。
2.从
logn 到1 倒序考虑每个整数p 对于每个
p ,若ans+2^p \le n 并且sum+c_{ans+2^p}<k ,则令ans=ans+2^p ,sum=sum+c_{ans+2^p} 。3.最后
ans+1 即为所求。
--来自李煜东《算法竞赛进阶指南》
但是权值线段树也有它的好处,就是它可以动态开点。
关于树状数组的复杂度
树状数组的真实查询复杂度应该是
也就是二进制下为一的位的个数。
最坏情况下当然就是
还有一点就是:
树状数组的形状像不像对数函数?
(不信你再看看前面我画的画)
而查询某个点的复杂度恰好就是这个点的高度。
而整棵树的平均高度为