虽然说:“一般普及组集训不会讲线段树,所以即便听不懂,也没有什么关系。”
但是我们也必须学好。
线段树是快速的查找某一个节点在若干条线段中出现的次数,RMQ也常常使用线段树来实现。
线段树是一种二叉搜索树 ,与区间树 相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度 为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。
—— 百度百科
eg1:《动态成绩查询》 一个班里有n 名学生,他们的学号是1~n
起初,所有人的成绩都是0,有m次操作。
数据范围是很经典的$10^{5}$。
建立一个树,根是1~n的区间和,左孩子和有孩子是1 ~ mid 和mid ~ n ,叶子节点为单个学生。
给x加分即给x的所有父节点加分,这就是线段树。
我们使用堆式存储存下整个树。
虽然说是有10w个节点,但是我们不能只开10w,我们要开2的整次幂,且接近10w。而$2^{17}$可以满足条件。
我们定义w[]存储一个节点的值。
单点加 我们单点加可以从叶子节点开始加,叶子节点加完后直接/2来到父节点。
1 2 3 4 5 6 7 8 9 void pointAdd (int pos, int k) { int cur = SIZE + pos - 1 ; while (cur != 0 ) { w[cur] += k; cur = cur / 2 ; } }
区间求和 如果要查询的区间就在自己的管辖范围之内,直接返回即可。如果不在就交给自己的孩子节点去做。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int querySum (int node, int fieldLe, int fieldRt, int queryLe, int queryRt) { if (queryRt < fieldLe || fieldRt < queryLe) return 0 ; if (queryLe <= fieldLe && fieldRt <= queryRt) return w[node]; int mid = (fieldLe + fieldRt) / 2 ; int sumLe = querySum (node*2 , fieldLe, mid, queryLe, queryRt); int sumRt = querySum (node*2 +1 , mid+1 , fieldRt, queryLe, queryRt); return sumLe + sumRt; }
eg2RMQ:洛谷P1816《忠诚》 我们在给区间加的时候顺手统计最小值即可。
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 #include <bits/stdc++.h> using namespace std;int n, m; void input () { cin >> n >> m; } #define SIZE 131072 int w[SIZE * 2 + 5 ]; void pointAdd (int node, int fieldLe, int fieldRt, int pos, int k) { w[node] += k; if (fieldLe == fieldRt) return ; int mid = (fieldLe + fieldRt) / 2 ; if (pos <= mid) pointAdd (node*2 , fieldLe, mid, pos, k); else pointAdd (node*2 +1 , mid+1 , fieldRt, pos, k); } int querySum (int node, int fieldLe, int fieldRt, int queryLe, int queryRt) { if (queryRt < fieldLe || fieldRt < queryLe) return 0 ; if (queryLe <= fieldLe && fieldRt <= queryRt) return w[node]; int mid = (fieldLe + fieldRt) / 2 ; int sumLe = querySum (node*2 , fieldLe, mid, queryLe, queryRt); int sumRt = querySum (node*2 +1 , mid+1 , fieldRt, queryLe, queryRt); return sumLe + sumRt; } void work () { while (m--) { int cmd, x, y; cin >> cmd >> x >> y; if (cmd == 1 ) pointAdd (1 , 1 , SIZE, x, y); else cout << querySum (1 , 1 , SIZE, x, y) << endl; } } int main () { input (); work (); return 0 ; }
This post was written on July 27, 2022