概念
用来维护区间信息的数据结构,可以在 O ( l o g N ) O(logN) O(logN)的时间复杂度内,实现单点修改,区间修改,区间查询等操作
1. 线段树的定义
//一般表示
#define MAXX 100007
int SegTree[MAXX<<2];
int Lazy[MAXX<<2]; //需要什么就增加相应的数组
int a[MAXX]; //原始数组
//结构体数组
#define MAXX 100007
int a[MAXX];
struct SegTreeNode
{
int val; //节点值
int lazy;
//需要什么在结构体中增加相应的元素
}SegTree[MAXX<<2];
2. 线段树的建立
数组[1,2,3,4,5]的线段树
线段树至少要开到原始数组的4倍(即:MAXX<<2),因为是一个树,要不断增加
//递归进行
void build(int l, int r, int p) //对[l,r]区间建立线段树,当前根的编号为p
{
if (l == r) // 到达叶子节点
tree[p] = a[l]; // 用数组中的数据赋值
else
{
int mid = (l + r) / 2;
build(l, mid, p * 2); // 先建立左右子节点 //一棵树左孩子是*2,右孩子*2+1
build(mid + 1, r, p * 2 + 1);
tree[p] = tree[p * 2] + tree[p * 2 + 1]; // 该节点的值等于左右子节点之和
}
}
递归建树过程:
2.单点修改,区间查询
如上图中的例子,想要知道区间[2,4]的值,可以从根节点开始查询,有三种情况:
- 无交集:不搭理
- 相交:左右哪边与目标区间有交集,搜索哪一边
- 包含:返回这个区间的值
int search(int i,int l,int r)
{
//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
//全=
if(tree[i].l>=l && tree[i].r<=r)
return tree[i].sum;
//如果这个区间和目标区间毫不相干,返回0
//无=
if(tree[i].r<l || tree[i].l>r)
return 0;
//相交
int s=0;
if(tree[i*2].r>=l)
s+=search(i*2,l,r);//如果这个区间的左儿子和目标区间又交集,那么搜索左儿子
if(tree[i*2+1].l<=r)
s+=search(i*2+1,l,r);//如果这个区间的右儿子和目标区间又交集,那么搜索右儿子
return s;
}
当更改一点的数值时
void add(int i,int dis,int k)
{
if(tree[i].l==tree[i].r)
{//如果是叶子节点,那么说明找到了
tree[i].sum+=k;
return ;
}
if(dis<=tree[i*2].r)
add(i*2,dis,k);//在哪往哪跑
else
add(i*2+1,dis,k);
tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//返回更新
return ;
}
3.区间修改+区间查询
如果要修改[1,1000000]区间,就要把里面的所有区间都修改一次,费时费力, 懒人标记法 随之诞生~
修改的时候,假设给每个数+k
- ① 如果是包含关系,不用到最底下了,只需知道有多少个数n,然后正在原来数字的基础上+nk,即可
- ② 如果是相交的关系,就下传懒人标记,直到是包含的关系
void add(int i,int l,int r,int k)
{
if(tree[i].r<=r && tree[i].l>=l)//如果当前区间被完全覆盖在目标区间里
{
tree[i].sum+=k*(tree[i].r-tree[i].l+1);
tree[i].lz+=k;//记录lazytage
return ;
}
push_down(i);//向下传递
if(tree[i*2].r>=l)
add(i*2,l,r,k);
if(tree[i*2+1].l<=r)
add(i*2+1,l,r,k);
tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;
return ;
}
其中的pushdown,就是把自己的lazytage归零,并给自己的儿子加上,并让自己的儿子加上k*(r-l+1)
void push_down(int i)
{
if(tree[i].lz!=0)
{
tree[i*2].lz+=tree[i].lz;//左右儿子分别加上父亲的lz
tree[i*2+1].lz+=tree[i].lz;
init mid=(tree[i].l+tree[i].r)/2;
tree[i*2].data+=tree[i].lz*(mid-tree[i*2].l+1);//左右分别求和加起来
tree[i*2+1].data+=tree[i].lz*(tree[i*2+1].r-mid);
tree[i].lz=0;//父亲lz归零
}
return ;
}
debug一次就理解了…
查询
int search(int i,int l,int r){
if(tree[i].l>=l && tree[i].r<=r)
return tree[i].sum;
if(tree[i].r<l || tree[i].l>r)
return 0;
push_down(i);
int s=0;
if(tree[i*2].r>=l)
s+=search(i*2,l,r);
if(tree[i*2+1].l<=r)
s+=search(i*2+1,l,r);
return s;
}