线段树——维基百科

2024-03-30 06:38
文章标签 线段 维基百科

本文主要是介绍线段树——维基百科,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

线段树[编辑]

维基百科,自由的百科全书

线段树是一种二叉树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。

目录

   [隐藏] 
  • 1基本操作
    • 1.1节点数据向上更新
    • 1.2节点懒惰标记下推
    • 1.3建树
    • 1.4更新
    • 1.5区间查询
  • 2变种
  • 3相关链接
  • 4参考资料

基本操作[编辑]

给定整个线段区间,建立一棵线段树的时间复杂度是 {\displaystyle O(N)}O(N)。单点修改的时间复杂度是 {\displaystyle O(\log N)}O(\log N) 。单点查询的时间复杂度是 {\displaystyle O(1)}O(1)。如果允许惰性赋值而加上延迟标记的话,许多的区间修改的时间复杂度也会是 {\displaystyle O(\log N)}O(\log N),但是单点查询的时间复杂度会变成 {\displaystyle O(\log N)}O(\log N)

代码中, rt指的是root, 当前子树的根节点; l, r指的是当前子树所统计的区间{\displaystyle [l,r]}[l, r] 利用完全二叉堆的性质来保存节点编号, 所以rt << 1是左子树的节点, rt << 1 | 1是右子树的节点 在查询和成端更新操作中的L和R是指修改或者查询的区间

节点数据向上更新[编辑]

将子节点的值更新到父节点。

/* 对于区间求和 */
void push_up(int rt) {tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
}/* 对于区间求最大值 */
void push_up(int rt) {tree[rt] = max(tree[rt << 1], tree[rt << 1 | 1]);
}

节点懒惰标记下推[编辑]

对于区间求和, 原子数组值需要加上lazy标记乘以子树所统计的区间长度。 len为父节点统计的区间长度, 则len - (len >> 1)为左子树区间长度, len >> 1为右子树区间长度。

void push_down(int rt, int len) {tree[rt << 1] += lazy[rt] * (len - (len >> 1));lazy[rt << 1] += lazy[rt];tree[rt << 1 | 1] += lazy[rt] * (len >> 1);lazy[rt << 1 | 1] += lazy[rt];lazy[rt] = 0;
}

对于区间求最大值, 子树的值不需要乘以长度, 所以不需要传递参数len。

void push_down(int rt) {tree[rt << 1] += lazy[rt];lazy[rt << 1] += lazy[rt];tree[rt << 1 | 1] += lazy[rt];lazy[rt << 1 | 1] += lazy[rt];lazy[rt] = 0;
}

建树[编辑]

新建一棵长度N的线段树。

#define lchild rt << 1, l, m
#define rchild rt << 1 | 1, m + 1, r
void build(int rt = 1, int l = 1, int r = N) {if (l == r) { std::cin >> tree[rt]; return; }int m = (l + r) >> 1;build(lchild); build(rchild);push_up(rt);
}

更新[编辑]

单点更新, 不需要用到lazy标记

#define lchild rt << 1, l, m
#define rchild rt << 1 | 1, m + 1, r
void update(int p, int delta, int rt = 1, int l = 1, int r = N) {if (l == r) {tree[rt] += delta;return;}int m = (l + r) >> 1;if (p <= m) update(p, delta, lchild);else update(p, delta, rchild);push_up(rt);
}

成段更新, 需要用到lazy标记来提高时间效率

#define lchild rt << 1, l, m
#define rchild rt << 1 | 1, m + 1, r
void update(int L, int R, int delta, int rt = 1, int l = 1, int r = N) {if (L <= l && r <= R) {tree[rt] += delta * (r - l + 1);lazy[rt] += delta;return;}if (lazy[rt]) push_down(rt, r - l + 1);int m = (l + r) >> 1;if (L <= m) update(L, R, delta, lchild);if (R > m)  update(L, R, delta, rchild);push_up(rt);
}

区间查询[编辑]

#define lchild rt << 1, l, m
#define rchild rt << 1 | 1, m + 1, r
int query(int L, int R, int rt = 1, int l = 1, int r = N) {if (L <= l && r <= R) return tree[rt];if (lazy[rt]) push_down(rt, r - l + 1);int m = (l + r) >> 1, ret = 0;if (L <= m) ret += query(L, R, lchild);if (R > m)  ret += query(L, R, rchild);return ret;
}

变种[编辑]

zkw线段树是一种自底向上的线段树,由清华大学的张昆玮提出。它相对于传统线段树的优势体现在减少了递归操作和增加了位运算等操作以减少常数[1]

相关链接[编辑]

http://dongxicheng.org/structure/segment-tree/

参考资料[编辑]

  1. ^ 张昆玮. 统计的力量——线段树全接触.

这篇关于线段树——维基百科的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/860751

相关文章

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

poj 1127 线段相交的判定

题意: 有n根木棍,每根的端点坐标分别是 px, py, qx, qy。 判断每对木棍是否相连,当他们之间有公共点时,就认为他们相连。 并且通过相连的木棍相连的木棍也是相连的。 解析: 线段相交的判定。 首先,模板中的线段相交是不判端点的,所以要加一个端点在直线上的判定; 然后,端点在直线上的判定这个函数是不判定两个端点是同一个端点的情况的,所以要加是否端点相等的判断。 最后

HDU4737线段树

题目大意:给定一系列数,F(i,j)表示对从ai到aj连续求或运算,(i<=j)求F(i,j)<=m的总数。 const int Max_N = 100008 ;int sum[Max_N<<2] , x[Max_N] ;int n , m ;void push_up(int t){sum[t] = sum[t<<1] | sum[t<<1|1] ;}void upd

zoj 1721 判断2条线段(完全)相交

给出起点,终点,与一些障碍线段。 求起点到终点的最短路。 枚举2点的距离,然后最短路。 2点可达条件:没有线段与这2点所构成的线段(完全)相交。 const double eps = 1e-8 ;double add(double x , double y){if(fabs(x+y) < eps*(fabs(x) + fabs(y))) return 0 ;return x + y ;

圆与线段的交点

poj 3819  给出一条线段的两个端点,再给出n个圆,求出这条线段被所有圆覆盖的部分占了整条线段的百分比。 圆与线段的交点 : 向量AB 的参数方程  P = A + t * (B - A)      0<=t<=1 ; 将点带入圆的方程即可。  注意: 有交点 0 <= t <= 1 ; 此题求覆盖的部分。 则 若求得 t  满足 ; double ask(d

Codeforces 482B 线段树

求是否存在这样的n个数; m次操作,每次操作就是三个数 l ,r,val          a[l] & a[l+1] &......&a[r] = val 就是区间l---r上的与的值为val 。 也就是意味着区间[L , R] 每个数要执行 | val 操作  最后判断  a[l] & a[l+1] &......&a[r] 是否= val import ja