线段树入门 洛谷【P1531】I Hate It

  我的第一篇真正意义上的博客呢。。。有点紧张>_<

【概述】

  线段树实际上是一棵完全二叉树,主要用于高效解决连续区间的动态查询问题(通过懒标记 lazy tag)。由于它二叉的结构,使得它的效率非常高(O(logn))。

  线段树的每一个节点都表示一个区间,它的左儿子和右儿子分别表示它的左右半区间。例如父节点代表[a,b],设c=(a+b)/2,则左儿子代表[a,c],右儿子代表[c+1,b]。下面我们通过一道例题来体会一下线段树。

 洛谷【P1531】I Hate It

【题目大意】

第一行输入两个数n,m分别代表数字个数和操作个数

第二行输入n个数,编号从1到n,分别代表这n个数的值

接下来m行有m个操作,格式如下:

  Q A B 询问编号从A到B中(包括A,B)的最大值

  U A B 若编号为A的数的值小于B,则将A的值改为B,否则不变

输入样例

5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5

输出样例

5
6
5
9

【解题步骤】

1.建树

  网上基本都是静态开点建线段树,需要的空间为4n。这里介绍一种动态开点的方法,只需要2n的空间。

 1 #define N 400100
 2 struct node {
 3     int l, r, lc, rc, max; //左端点,右端点,左儿子,右儿子,区间最大值(懒标记) 
 4 } tr[N];
 5 void pushup(int l, int r, int rt) {
 6     tr[rt].max = max(tr[l].max, tr[r].max); //懒标记上浮 
 7 }
 8 void build(int l, int r, int & rt) {
 9     rt = ++tot;
10     tr[rt].l = l, tr[rt].r = r; //此题并不需要这行代码,但有的线段树会用到 
11     if (l == r) { //找到叶子结点
12         scanf("%d", &tr[rt].max);
13         return;
14     }
15     int mid = (l + r) >> 1; //位运算,相当于 (l + r) / 2 
16     build(l, mid, tr[rt].lc); //建左子树 
17     build(mid + 1, r, tr[rt].rc); //建右子树 
18     pushup(tr[rt].lc, tr[rt].rc, rt);
19 }

2.查询

 1 int getans(int l, int r, int rt) {
 2     if (a <= l && b >= r) //查询区间覆盖当前区间 
 3         return tr[rt].max;
 4     int ans = -99999999, mid = (l + r) >> 1;
 5     if (a <= mid) //二分查找左儿子 
 6         ans = max(ans, getans(l, mid, tr[rt].lc));
 7     if (b > mid) //查找右儿子 
 8         ans = max(ans, getans(mid + 1, r, tr[rt].rc));
 9     return ans;
10 }

3.修改

 1 void update(int l, int r, int rt) {
 2     if (l == r) { //找到要修改的叶子结点 
 3         if (tr[rt].max < b)
 4             tr[rt].max = b;
 5         return;
 6     }
 7     int mid = (l + r) >> 1;
 8     if (a <= mid)
 9         update(l, mid, tr[rt].lc);
10     else //不在左边就在右边 
11         update(mid + 1, r, tr[rt].rc);
12     pushup(tr[rt].lc, tr[rt].rc, rt); //别忘了上浮 
13 }

接下来上完整代码辣^w^

 1 #include <cstdio>
 2 #include <iostream>
 3 using namespace std;
 4 #define N 400100
 5 int n, m, root, tot, a, b;
 6 char c;
 7 struct node {
 8     int l, r, lc, rc, max; //左端点,右端点,左儿子,右儿子,区间最大值(懒标记) 
 9 } tr[N];
10 void pushup(int l, int r, int rt) {
11     tr[rt].max = max(tr[l].max, tr[r].max); //懒标记上浮 
12 }
13 void build(int l, int r, int & rt) {
14     rt = ++tot;
15     tr[rt].l = l, tr[rt].r = r; //此题并不需要这行代码,但有的线段树会用到 
16     if (l == r) { //找到叶子结点 
17         scanf("%d", &tr[rt].max);
18         return;
19     }
20     int mid = (l + r) >> 1; //位运算,相当于 (l + r) / 2 
21     build(l, mid, tr[rt].lc); //建左子树 
22     build(mid + 1, r, tr[rt].rc); //建右子树 
23     pushup(tr[rt].lc, tr[rt].rc, rt);
24 }
25 int getans(int l, int r, int rt) {
26     if (a <= l && b >= r) //查询区间覆盖当前区间 
27         return tr[rt].max;
28     int ans = -99999999, mid = (l + r) >> 1;
29     if (a <= mid) //二分查找左儿子 
30         ans = max(ans, getans(l, mid, tr[rt].lc));
31     if (b > mid) //查找右儿子 
32         ans = max(ans, getans(mid + 1, r, tr[rt].rc));
33     return ans;
34 }
35 void update(int l, int r, int rt) {
36     if (l == r && l == a) { //找到要修改的叶子结点 
37         if (tr[rt].max < b)
38             tr[rt].max = b;
39         return;
40     }
41     int mid = (l + r) >> 1;
42     if (a <= mid)
43         update(l, mid, tr[rt].lc);
44     else //不在左边就在右边 
45         update(mid + 1, r, tr[rt].rc);
46     pushup(tr[rt].lc, tr[rt].rc, rt); //别忘了上浮 
47 }
48 int main() {
49     scanf("%d %d", &n, &m); //大数据用scanf效率更高 
50     build(1, n, root);
51     while (m--) {
52         scanf("\n%c %d %d", &c, &a, &b); //换行符别忘了读 
53         if (c == 'Q')
54             printf("%d\n", getans(1, n, 1));
55         else
56             update(1, n, 1);
57     }
58     return 0;
59 }

以上就是线段树的入门了(然而还有一些神奇的操作没讲QAQ)。因为是第一篇博客,所以肯定有许多不足,望各位神犇勿喷。谢谢!QWQ

猜你喜欢

转载自www.cnblogs.com/zcxqiangwudi/p/9124125.html
今日推荐