线段树入门 洛谷【P3372】【模板】线段树1
我的第一篇真正意义上的博客呢。。。有点紧张>_<
【概述】
线段树实际上是一棵完全二叉树,主要用于高效解决连续区间的动态查询问题(通过懒标记 lazy tag)。由于它二叉的结构,使得它的效率非常高(O(logn))。
线段树的每一个节点都表示一个区间,它的左儿子和右儿子分别表示它的左右半区间。例如父节点代表[a,b],设c=(a+b)/2,则左儿子代表[a,c],右儿子代表[c+1,b]。下面我们通过一道例题来体会一下线段树。
洛谷【P3372】【模板】线段树1
【题目大意】
第一行输入两个数n,m分别代表数字个数和操作个数(n<=100000, m<=100000)
第二行输入n个数,编号从1到n,分别代表这n个数的值
接下来m行有m个操作,格式如下:
1 x y k 将区间[x,y]内每个数加上k
2 x y 询问区间[x,y]内每个数的和
(保证在int64/long long数据范围内)
输入样例
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例
11
8
20
【解题步骤】
1.建树
网上基本都是静态开点建线段树,需要的空间为4n。这里介绍一种动态开点的方法,只需要2n的空间。
1 #define N 200100 //开两倍空间 2 struct node { 3 int l, r, lc, rc; //左端点,右端点,左儿子,右儿子 4 long long v, tag; //区间和,懒标记 5 } tr[N]; 6 void pushup(int lc, int rc, int rt) { //区间和上浮 7 tr[rt].v = tr[lc].v + tr[rc].v; 8 } 9 void build(int l, int r, int & rt) { 10 rt = ++tot; 11 tr[rt].l = l, tr[rt].r = r; //记录该节点区间端点 12 if (l == r) { //叶子结点 13 scanf("%lld", &tr[rt].v); //读入大数据用scanf更快 14 return; 15 } 16 int mid = (l + r) >> 1; //位运算,相当于(l + r) / 2 17 build(l, mid, tr[rt].lc); //建左子树 18 build(mid + 1, r, tr[rt].rc); //建右子树 19 pushup(tr[rt].lc, tr[rt].rc, rt); //上浮 20 }
这里有一个上浮操作,是为了之后查询时只要找到覆盖的区间返回区间和就可以了,而不用一直找到叶子结点(详细代码参看 3.查询)。
2.修改
1 void pushdown(int lc, int rc, int rt) { //懒标记下沉 2 tr[lc].tag += tr[rt].tag; //注意是加上父节点的懒标记 3 tr[rc].tag += tr[rt].tag; 4 tr[lc].v += (tr[lc].r - tr[lc].l + 1) * tr[rt].tag; //修改区间和 5 tr[rc].v += (tr[rc].r - tr[rc].l + 1) * tr[rt].tag; 6 tr[rt].tag = 0; //清零,防止干扰下次操作 7 } 8 void update(int l, int r, int rt) { 9 if (x <= l && y >= r) { //区间覆盖,[x,y]表示修改区间 10 tr[rt].tag += k; //修改懒标记 11 tr[rt].v += (r - l + 1) * k; //区间和加上区间长度与k的乘积 12 return; 13 } 14 pushdown(tr[rt].lc, tr[rt].rc, rt); //下沉 15 int mid = (l + r) >> 1; 16 if (x <= mid) 17 update(l, mid, tr[rt].lc); //修改左子树 18 if (y > mid) 19 update(mid + 1, r, tr[rt].rc); //修改右子树 20 pushup(tr[rt].lc, tr[rt].rc, rt); //上浮 21 }
懒标记表示的是这整段区间都加上的值,因此在修改某段区间的一部分时懒标记要下沉(因为不再是同一个值了)。
3.查询
1 long long getans(int l, int r, int rt) { 2 if (x <= l && y >= r) //[x,y]表示查询区间 3 return tr[rt].v; //返回区间和 4 pushdown(tr[rt].lc, tr[rt].rc, rt); //下沉 5 long long ans = 0; 6 int mid = (l + r) >> 1; 7 if (x <= mid) 8 ans += getans(l, mid, tr[rt].lc); 9 if (y > mid) 10 ans += getans(mid + 1, r, tr[rt].rc); 11 return ans; 12 }
接下来上完整代码辣^w^
1 #include <cstdio> 2 using namespace std; 3 #define N 200100 //开两倍空间 4 int n, m, root, tot, p, x, y; 5 long long k; 6 struct node { 7 int l, r, lc, rc; //左端点,右端点,左儿子,右儿子 8 long long v, tag; //区间和,懒标记 9 } tr[N]; 10 void pushup(int lc, int rc, int rt) { //区间和上浮 11 tr[rt].v = tr[lc].v + tr[rc].v; 12 } 13 void pushdown(int lc, int rc, int rt) { //懒标记下沉 14 tr[lc].tag += tr[rt].tag; //注意是加上父节点的懒标记 15 tr[rc].tag += tr[rt].tag; 16 tr[lc].v += (tr[lc].r - tr[lc].l + 1) * tr[rt].tag; //修改区间和 17 tr[rc].v += (tr[rc].r - tr[rc].l + 1) * tr[rt].tag; 18 tr[rt].tag = 0; //清零,防止干扰下次操作 19 } 20 void build(int l, int r, int & rt) { 21 rt = ++tot; 22 tr[rt].l = l, tr[rt].r = r; //记录该节点区间端点 23 if (l == r) { //叶子结点 24 scanf("%lld", &tr[rt].v); //读入大数据用scanf更快 25 return; 26 } 27 int mid = (l + r) >> 1; //位运算,相当于(l + r) / 2 28 build(l, mid, tr[rt].lc); //建左子树 29 build(mid + 1, r, tr[rt].rc); //建右子树 30 pushup(tr[rt].lc, tr[rt].rc, rt); //上浮 31 } 32 void update(int l, int r, int rt) { 33 if (x <= l && y >= r) { //区间覆盖,[x,y]表示修改区间 34 tr[rt].tag += k; //修改懒标记 35 tr[rt].v += (r - l + 1) * k; //区间和加上区间长度与k的乘积 36 return; 37 } 38 pushdown(tr[rt].lc, tr[rt].rc, rt); //下沉 39 int mid = (l + r) >> 1; 40 if (x <= mid) 41 update(l, mid, tr[rt].lc); //修改左子树 42 if (y > mid) 43 update(mid + 1, r, tr[rt].rc); //修改右子树 44 pushup(tr[rt].lc, tr[rt].rc, rt); //上浮 45 } 46 long long getans(int l, int r, int rt) { 47 if (x <= l && y >= r) //[x,y]表示查询区间 48 return tr[rt].v; //返回区间和 49 pushdown(tr[rt].lc, tr[rt].rc, rt); //下沉 50 long long ans = 0; 51 int mid = (l + r) >> 1; 52 if (x <= mid) 53 ans += getans(l, mid, tr[rt].lc); 54 if (y > mid) 55 ans += getans(mid + 1, r, tr[rt].rc); 56 return ans; 57 } 58 int main() { 59 scanf("%d %d", &n, &m); 60 build(1, n, root); 61 while (m--) { 62 scanf("%d %d %d", &p, &x, &y); 63 if (p == 1) { 64 scanf("%lld", &k); 65 update(1, n, 1); 66 } else 67 printf("%lld\n", getans(1, n, 1)); 68 } 69 return 0; 70 }
以上就是线段树的入门了(然而还有一些神奇的操作没讲QAQ)。因为是第一篇博客,所以肯定有许多不足,望各位神犇勿喷。谢谢!QwQ