线段树可以解决什么问题?对于[L, R]区间,它的答案可以由[L, M]和[M + 1, R]合并求出,具有这种性质的问题适合用线段树来解决。例如区间求和,大区间的和可以直接由两个子区间的和相加得到;还有区间最大/最小值问题,也可以比较两个子区间的最大/最小值,选出更大/更小的那一个作为结果。
不适合解决什么问题?区间众数,区间最长连续,最长不下降问题。除此之外,线段树似乎也不支持数据插入,因为插入会破坏平衡树的结构?
线段树主要有几个操作:
一、建树
以堆方式存储
二、单点修改/区间修改
区间修改后的查询要用到lazy标记,lazy标记是线段树的精髓所在,如果在区间修改的时候不采用lazy标记,而是搜索这个区间中的节点一一修改,那么与常规暴力遍历修改的时间复杂度其实拉不开差距。
lazy标记的具体使用过程如下,在区间修改时,如果修改的区间完全覆盖了某区间,那么就把这个区间打上lazy标记,这个区间囊括的子区间则暂时不更新,由后续查询遍历到子区间时检测到lazy标记做懒更新。
三、区间查询
四、与树状数组的异同
优点:
- 线段树的区间修改的时间复杂度仍然是O(logn),而树状数组的区间修改的时间复杂度是O(klogn),k为区间元素个数
- 线段树的应用范围更广,满足区间运算性质的问题都可以用线段树来求,例如区间最大/小值和区间和等等,而树状数组只能用来求区间和
缺点:
例题P3372 【模板】线段树 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| public class SegmentTree { private final long[] data; private final Node[] tree;
SegmentTree(long[] data) { this.data = data; tree = new Node[data.length * 4]; }
private static class Node { int l; int r; long sum; long lazy; }
public void build(int i, int l, int r) { tree[i] = new Node(); tree[i].l = l; tree[i].r = r; if (l == r) { tree[i].sum = data[r]; return; } int mid = l + (r - l) >> 1; build(i << 1, l, mid); build((i << 1) | 1, mid + 1, r); tree[i].sum = tree[i << 1].sum + tree[(i << 1) | 1].sum; }
public void update(int i, int l, int r, int k) { if (tree[i].l >= l && tree[i].r <= r) { tree[i].sum += (long) k * (l - r + 1); tree[i].lazy += k; return; } if (tree[i].lazy != 0) { pushDown(i); } if (tree[i << 1].r >= l) { update(i << 1, l, r, k); } if (tree[(i << 1) | 1].l <= r) { update((i << 1) | 1, l, r, k); } tree[i].sum = tree[i << 1].sum + tree[(i << 1) | 1].sum; }
public void pushDown(int i) { long lazy = tree[i].lazy; tree[i << 1].lazy += lazy; tree[(i << 1) + 1].lazy += lazy; int mid = tree[i].l + (tree[i].r - tree[i].l) >> 1; tree[i << 1].sum += lazy * (mid - tree[i].l + 1); tree[(i << 1) + 1].sum += lazy * (tree[i].r - mid); tree[i].lazy = 0; }
public long query(int i, int l, int r) { if (tree[i].r <= r && tree[i].l >= l) { return tree[i].sum; } if (tree[i].lazy != 0) { pushDown(i); } long temp = 0; if (tree[i << 1].r >= l) { temp += query(i << 1, l, r); } if (tree[(i << 1) | 1].l <= r) { temp += query((i << 1) | 1, l, r); } return temp; } }
|