[集训笔记5th]树状数组与线段树的入门
树状数组
树状数组(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)+1xa[i]。
事实上, 数组ccc可以看做成一个树形结构,该结构满足以下性质:
- 每个内部节点c[x]c[x]c[x]保存以它为根的子树中所有叶节点的和。
- 每个内部节点c[x]c[x]c[x]的子节点个数等于lowbit(x)lowbit(x)lowbit(x)的个数(lowbit(x)=x&(−x)lowbit(x) = x \& (-x)lowbit(x)=x&(−x))。
- 除了树根外,每个内部节点c[x]c[x]c[x]的父节点是c[x+lowbit(x)]c[x+lowbit(x)]c[x+lowbit(x)]。
- 树的深度为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[p]以求区间和为例:.val = max(c[p<<1].val, c[p<<1|1].val); }
void pushup(int p){ c[p]在精准更新某个特定区间后, 所有以这个区间为子区间的区间都要依次更新。.val = c[p<<1].val + c[p<<1].val; }
线段树的单元
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; }
- [PKU暑课笔记] 趁机膜一发线段树和树状数组
- HDU 1166 敌兵布阵【线段树,树状数组入门题,单点更新,区间求和】
- Android笔记1 Android入门
- TensorFlow入门及实战笔记——tensorboard
- Android 开发笔记——环境入门
- HBase 入门笔记-数据落地篇
- 【php学习】PHP 入门经典第一章笔记
- ElasticSearch 菜鸟笔记 (一)ElasticSearch 入门简介
- HDU 1166 敌兵布阵 (线段树 & 树状数组)
- 小结:线段树 & 主席树 & 树状数组
- 蓝鸥Unity入门鼠标事件学习笔记
- Hadoop学习笔记(1) ——菜鸟入门
- [poj2104]可持久化线段树入门题(主席树)
- linux设备驱动开发详解 阅读笔记1(第一篇入门)
- [原]java专业程序代写(qq:928900200),学习笔记之基础入门<Hibernate_配置详解>(三十六)
- http协议和servlet入门笔记
- [笔记]我的Linux入门之路 - 03.Java环境搭建
- Java入门笔记5_异常
- jquery入门学习笔记(1)
- C# 从入门到精通 学习笔记1 第2章 使用变量、操作符和表达式