首页 > 其他分享 >线段树----区间问题的神

线段树----区间问题的神

时间:2022-08-16 00:00:26浏览次数:49  
标签:int 线段 tr ---- 修改 区间 include

《标准线段树》

 

 普通的线段树,其区间一般都是一个序列的数的个数,但还是要根据不同题目来判断

注意:tr[]空间是N*4,N为最大范围

《单点修改,区间查询》

原题:https://www.acwing.com/problem/content/1277/

 

 

 

 像这道题,N最大为2*1e5,我们可以事先建立一颗最大范围1~N的线段树,然后不断分裂这个范围,产生子节点,

每一个节点维护的信息是,这个节点代表的区间的最大值,这里范围代表数的个数;

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 using namespace std;
 5 typedef long long LL;
 6 const int N = 200010;
 7 struct tree
 8 {
 9     int l, r;
10     //区间[l,r]中的最大值
11     int v;
12 } tr[N * 4];
13 //由子节点维护的信息计算(或更新)父节点维护的信息
14 void pushup(int u)
15 {
16     //解释一下 u<<1|1这个操作:
17     //对于2的倍数,其二进制下的第一位总是0,根据这个特性|1,相当于+1;
18     tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
19 }
20 //将tr[u]这个位置初始化为树
21 void build(int u, int l, int r)
22 {
23     tr[u].l = l, tr[u].r = r;
24     if (l == r)
25         return;
26     //维护信息根据题目的不同,在代码中的写法也不同
27     //这里因为是以逐渐加点(在modify中)来产生信息,
28     //在最开始的建树过程是无维护信息的,所以这里不管tr[u].v;
29     int mid = l + r >> 1;
30     build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
31 }
32 //单点修改,将tr[u]包含区间中的位置为x的值修改为v;
33 void modify(int u, int x, int v)
34 {
35     if (tr[u].l == x && tr[u].r == x)
36         tr[u].v = v;
37     else
38     {
39         int mid = tr[u].l + tr[u].r >> 1;
40         if (x <= mid)
41             modify(u << 1, x, v);
42         else
43             modify(u << 1 | 1, x, v);
44         //注意这个pushup()操作一定要写到这里,而不是写到外面
45         // pushup操作是更新父节点u的维护信息,其是写在子节点u<<1,u<<1|1被更新时写的
46         //如果写在外面,当 if (tr[u].l == x && tr[u].r == x)tr[u].v = v; 生效时
47         //这个时候u是无子节点的,那就会问:这个u点更新了,那如何更新其父节点呢?
48         //很简单,因为在modify时,我们会传入的u是1,即根节点,此后都是递归去更新
49         pushup(u);
50     }
51 }
52 //查询在点u包含的范围内中[l,r]的维护的信息
53 int query(int u, int l, int r)
54 {
55     if (tr[u].l >= l && tr[u].r <= r)
56         return tr[u].v;
57     //注意这里v是取题中数的最小值
58     int v = 0;
59     int mid = tr[u].l + tr[u].r >> 1;
60     if (l <= mid)
61         v = query(u << 1, l, r);
62     //注意:这里就不是else了
63     if (r > mid)
64         v = max(v, query(u << 1 | 1, l, r));
65     return v;
66 }
67 int main()
68 {
69     int m, p, a = 0, x, n = 0;
70     cin >> m >> p;
71     char op[2];
72     build(1, 1, m);
73     while (m--)
74     {
75         scanf("%s%d", op, &x);
76         if (op[0] == 'A')
77         {
78             //注意这里可能爆int;
79             modify(1, n + 1, ((LL)a + x) % p);
80             n++;
81         }
82         else
83         {
84             a = query(1, n - x + 1, n);
85             printf("%d\n", a);
86         }
87     }
88     return 0;
89 }

《时间复杂度》

《区间修改,区间查询》

对于区间修改,tree结构体中要多一个变量lazy,用来记录整个区间的修改,是缓存下放到修改子区间的值

注意:不是缓存修改本区间,本区间的维护的信息是要随时修改的

  1 #include <iostream>
  2 #include <algorithm>
  3 #include <cstring>
  4 using namespace std;
  5 const int N = 100010;
  6 typedef long long LL;
  7 int w[N];
  8 struct tree
  9 {
 10     // sum是区间[l,r]要维护的信息
 11     // lazy是在多次修改时,将修改的数先累加起来,即先不下放修改给子区间
 12     //到这个区间的lazy不平衡时才下放(如[1,10],先整个区间+10,再[5,10]区间+5,这个时候区间lazy不能平衡,要下放)
 13     //当前的sum是可以即时修改的
 14     int l, r, lazy;
 15     LL sum;
 16 } tr[N * 4];
 17 //用子节点更新父节点u的操作
 18 void pushup(int u)
 19 {
 20     tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
 21 }
 22 //将父节点u的lazy标记下放的操作
 23 void pushdown(int u)
 24 {
 25     //如果积累下来的lazy有修改:
 26     if (tr[u].lazy)
 27     {
 28         int sl = u << 1, sr = u << 1 | 1;
 29         //注意lazy这里是+=
 30         tr[sl].lazy += tr[u].lazy;
 31         //注意可能会爆int
 32         tr[sl].sum += (LL)(tr[sl].r - tr[sl].l + 1) * tr[u].lazy;
 33 
 34         tr[sr].lazy += tr[u].lazy;
 35         tr[sr].sum += (LL)(tr[sr].r - tr[sr].l + 1) * tr[u].lazy;
 36         //全部下放成功,清空:
 37         tr[u].lazy = 0;
 38     }
 39 }
 40 void build(int u, int l, int r)
 41 {
 42     tr[u].l = l, tr[u].r = r;
 43 
 44     if (l == r)
 45     {
 46         tr[u].lazy = 0;
 47         tr[u].sum = w[l];
 48         return;
 49     }
 50     int mid = l + r >> 1;
 51     build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
 52     //子节点变化,pushup
 53     pushup(u);
 54 }
 55 //区间修改
 56 void modify(int u, int l, int r, int d)
 57 {
 58     //注意这里与单点修改有着极不一样的地方
 59     if (tr[u].l >= l && tr[u].r <= r)
 60     {
 61         //注意这里tr[u].sum是+=
 62         tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
 63         tr[u].lazy += d;
 64         return;
 65     }
 66     // u管辖的区间太大,接下来区间分裂,直到出现上面的情况
 67     //区间分裂说明,在u管辖的区间只会有一部分区间加上修改,lazy不平衡,要pushdown();
 68     pushdown(u);
 69     int mid = tr[u].l + tr[u].r >> 1;
 70     if (l <= mid)
 71         modify(u << 1, l, r, d);
 72     if (r > mid)
 73         modify(u << 1 | 1, l, r, d);
 74     //上面u的子节点发生的变化,要pushup
 75     pushup(u);
 76 }
 77 //区间询问
 78 LL query(int u, int l, int r)
 79 {
 80     if (tr[u].l >= l && tr[u].r <= r)
 81         return tr[u].sum;
 82     //当要分裂询问时
 83     //当然要懒标记下放,要不然父节点一直拿到修改值而不去修改也没用
 84     pushdown(u);
 85     int mid = tr[u].l + tr[u].r >> 1;
 86     LL res = 0;
 87     if (l <= mid)
 88         res += query(u << 1, l, r);
 89     if (r > mid)
 90         res += query(u << 1 | 1, l, r);
 91     return res;
 92 }
 93 int main()
 94 {
 95     int n, m;
 96     cin >> n >> m;
 97     for (int i = 1; i <= n; i++)
 98         scanf("%d", &w[i]);
 99     build(1, 1, n);
100     while (m--)
101     {
102         char op[2];
103         int l, r, d;
104         scanf("%s%d%d", op, &l, &r);
105         if (op[0] == 'Q')
106             printf("%lld\n", query(1, l, r));
107         else
108         {
109             scanf("%d", &d);
110             modify(1, l, r, d);
111         }
112     }
113     return 0;
114 }
115 //关于为啥modify哪里要pushdown,再pushup的原因
116 /* 由于modify直接修改了一些区间上面的add与sum,导致递归路径上的父节点全部都需要重新算一遍sum,所以递归结尾需要加一个pushup,
117 
118 同时递归路径上如果存在带有懒标记的区间,则区间结尾的pushup会用两个子区间的返回值直接覆盖该区间的sum,但是该区间的懒标记add值还没有加到子区间上面去,也就是说懒标记的附加值就会被直接覆盖掉。
119 这种pushup会直接消灭懒标记,所以modify递归路径上必须全部消灭懒标记,即每一层递归都要先pushdown。
120 
121 这里可以看到pushup和pushdown是一个组合,递归中每一层pushup保证了递归树的叶节点的改变对父节点的影响能够及时修正,每一层的pushdown都能保证本次的递归路径上一定不存在懒标记,保证本次递归路径上的sum赋值全部为正确值。
122 
123 所以尽管查询操作并没有新增懒标记且保证递归路径上没有懒标记导致我们不需要pushup,但当我们在query的时候在每一层返回sum之前加一次pushup上述的一套组合依然能保证查询结果一定是正确的,
124 
125 所以可以从这一套组合的性质出发直接 背下来,在简单运用线段树模板时候直接modify和query都用这一套,就省事很多了。
126  */
127 //lazy不是对本区间的维护信息延迟,而是对本区间的子区间延迟
128 //本区间维护的信息是要时刻保持最新

《时间复杂度》

《小总结》

 

 区间询问这里是真的要根据题目的不同而变化的,要考虑如何才能正确的维护信息

《带权线段树》

《可持久化线段树----主席树》

标签:int,线段,tr,----,修改,区间,include
From: https://www.cnblogs.com/cilinmengye/p/16590147.html

相关文章

  • 《GB28379-2012》PDF下载
    《GB28379-2012便器冲洗阀用水效率限定值及用水效率等级》PDF下载《GB28379-2012》简介本标准规定了机械式便器冲压阀、压力式便器冲洗阀、非接触式便器冲洗阀的用水效......
  • 《GB28373-2012》PDF下载
    《GB28373-2012N类和O类罐式车辆侧倾稳定性》PDF下载《GB28373-2012》简介本标准规定了N和O类罐式车辆侧倾稳定性的术语、定义、限值要求、实车试验法、模拟计算法、......
  • org.apache.ibatis.exceptions.PersistenceException: ### Error building SqlSessio
    解决方案:改为......
  • [AcWing 145] 超市
    贪心+小根堆点击查看代码#include<bits/stdc++.h>usingnamespacestd;typedeflonglongLL;typedefpair<int,int>PII;constintN=1e6+10;intn;......
  • 正则详细讲解
       正则表达式(regularexpression)就是用一个“字符串”来描述一个特征,然后去验证另一个“字符串”是否符合这个特征。比如表达式“ab+”描述的特征是“一个'a'和任......
  • vue简单实现一个table组件
    看到elementUI封装的el-table组件觉得很有意思,就自己简单实现了自己的一个table组件,具体功能有 columns:表头 data:数据 border:是否有边框 zebra:是否有斑马纹 ......
  • mysql和navicat的安装和使用
    昨天把Pycharm安装好了,今天开始安装mysql 数据库。MySql如果电脑上第一次安装mysql,会让注册一个Oracle帐户,浏览器输入:mysqlforwindows 就可以找到,新版本......
  • 在IIS中部署.NET Core WebApi程序(转载)
    环境说明部署NETCore编写WebApi并部署为IIS站点,演示环境如下:VisualStudio2019(v16.8).NetCore3.1一台安装了IIS的设备Note:.NETCore3.0项目开发需要vs2019(......
  • C语言等长编码压缩和哈夫曼编码压缩
    C语言等长编码压缩和哈夫曼编码压缩利用哈夫曼算法对文件进行压缩及解压缩题目:选择一个英文纯文本文档(不少于3千字,也可以更多),分别利用等长编码和哈夫曼编码对其进行......
  • 《GB3095-2012》PDF下载
    《GB3095-2012环境空气质量标准》PDF下载《GB3095-2012》简介本标准规定了环境空气功能区分类、标准分级、污染物项目、平均时间及浓度限值、监测方法、数据统计的有效......