您的位置:首页 > 其它

[集训笔记5th]树状数组与线段树的入门

2019-08-06 11:12 134 查看
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_43279710/article/details/98333902

树状数组

树状数组(BinaryIndexedTreesBinary Indexed TreesBinaryIndexedTrees)是一种由于维护序列前缀和的数据结构。对于给定序列aaa,我们建立一个数组ccc,其中c[x]c[x]c[x]保存序列aaa的区间[x−lowbit(x)+1,x][x - lowbit(x)+1, x][x−lowbit(x)+1,x]中所有数的和,即∑i=x−lowbit(x)+1xa[i]\sum^{x}_{i = x-lowbit(x)+1}{a[i]}∑i=x−lowbit(x)+1x​a[i]。
事实上, 数组ccc可以看做成一个树形结构,该结构满足以下性质:

  1. 每个内部节点c[x]c[x]c[x]保存以它为根的子树中所有叶节点的和。
  2. 每个内部节点c[x]c[x]c[x]的子节点个数等于lowbit(x)lowbit(x)lowbit(x)的个数(lowbit(x)=x&(−x)lowbit(x) = x \& (-x)lowbit(x)=x&(−x))。
  3. 除了树根外,每个内部节点c[x]c[x]c[x]的父节点是c[x+lowbit(x)]c[x+lowbit(x)]c[x+lowbit(x)]。
  4. 树的深度为O(logN)O(logN)O(logN)。

树状数组查询前缀和

int ask(int x){
int ans = 0;
for(; x; x -= x&-x) ans += c[x];
return ans;
}

树状数组单点增加同时维护前缀和

void add(int x, int y){
for(; x <= N; x += x & -x) c[x] += y;
}

在执行所有操作之前, 我们需要对树状数组进行初始化——针对原始序列aaa构造一个树状数组。
方法是:从小到大一次考虑每个节点xxx,借助lowbitlowbitlowbit运算扫描它的子节点并求和。时间复杂度为O(N)O(N)O(N)。

线段树

线段树(SegmentSegmentSegment TreeTreeTree)是一种基于分治思想的二叉树, 用于在区间上进行信息统计, 可存储复杂信息, 比树状数组更通用, 但是代码量较大。
1.线段树的每个节点都代表着一个空间。
2.线段树具有唯一的根节点, 代表的区间是整个统计范围, 如[1, N]。
3.线段树的每个叶子节点都代表一个长度为1的区间[1, x]。
4.对于每个内部节点[l, r],它的左子节点是[l, mid], 右子节点是[mid+1,r],其中mid = (l + r) / 2(向下取整)。

线段树的向上更新

以求区间最大值为例:

void pushup(int p){
c

.val = max(c[p<<1].val, c[p<<1|1].val); }

[p]以求区间和为例:

void pushup(int p){
c

.val = c[p<<1].val + c[p<<1].val; }

[p]在精准更新某个特定区间后, 所有以这个区间为子区间的区间都要依次更新。

线段树的单元

struct Node{
int l, r;//保存该区间的左右边界
int val;//保存该区间的个体信息
}

线段树的建立

相当于对数组维护的初始化

void build(int p, int l, int r){
tree

.l = l, tree[p].r = r; if(l == r)//更新到叶子节点 { tree[p].val = a[l]; return ; } int mid = (l + r) >> 1; build(p<<1, l, mid); build(p<<1|1, mid + 1, r);//对对应某区间的节点的两个子节点更新 pushup(p); }

线段树的区间查询

[p]较灵活,可以根据题意进行灵活地改动
这里以求某段区间中的最大值为例(于是用这个模板也可以求出最小值和极差)

int ask(int p, int l, int r){
if(l <= tree

.l && r >= tree[p].r) return t[p].val; int mid = (t[p].l + t[p].r) >> 1; int va =-(1 << 30); if(l<=mid) va = max(va, ask(p<<1, l, r)); if(r> mid) va = max(va, ask(p<<1|1, l, r)); return va; }

线段树的单点修改

void change(int p, int x, int v){
if(t[p].l == t[p].r) {t[p]. val = v; return;}
int mid = (t[p].l + t[p].r) / 2;
if(x <= mid) change(p<<1, x, v);
else change(p<<1|1, x, v);
t[p].val = max(t[p<<1].val, t[p<<1|1].val);
}

线段树的区间修改

[p]为了节省时间, 通常在结构体里多维护一个变量lazy标记,这样可以减少更新子节点的次数。注意有的题目要求要对每个叶子节点更新,就不可以用lazy标记。有的题目里可能本身的信息就可以充当lazy标记起到作用,就可以不用多维护变量。

void pushdown(int p){//向下更新lazy标记
if(t[p].lazy){
t[p<<1].val += t[p].lazy * (t[p<<1].r - t[p<<1].l + 1);
t[p<<1|1].val += t[p<<1|1].lazy * (t[p<<1|1].r - t[p].l + 1);
}
t[p<<1].lazy += t[p].lazy;
t[p<<1|1].lazy += t[p].lazy;
t[p<<1].lazy = 0;
}

void change(int p, int l, int r, int d){
if(l <= t[p].l && r >= t[p].r){
t[p].val += d * (t[p].r - t[p].r + 1);
t[p].lazy += d;
return ;
}
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) change(p<<1, l, r, d);
if (r > mid) change(p<<1|1, l, r, d);
pushup(p);
}

int ask(int p, int l, int r){
if (l <= t[p].l && r >= t[p].r) return t[p].val;
pushdown(p);
int mid = (t[p].l + t[p].r) >> 1;
int val = 0;
if (l <= mid) val += ask(p<<1, l, r);
if (r > mid) val += ask(p<<1|1, l, r);
return val;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: