线段树(知识篇)
线段树,从入门到入坑
- xyz的小窝 - 洛谷博客 (luogu.com.cn)
算法学习笔记(14):
线段树 - 知乎 (zhihu.com)
如何快速求出一个序列的区间和呢?可以使用前缀和。如何快速求出一个序列的最值呢?可
以使用 ST
表。这两种数据结构在建立的时候颇费功夫,但使用的时候效率很高。如果再增加一
个需求:时不时的修改序列的值,那么这两种数据结构就无法高效完成了。不过可以使用线段树
来解决这类问题。
在基础篇中,我们已经学习了二叉树的有关概念。而线段树是一种特殊的二叉树,它可以将
一个线性的序列组织成一个树状的结构,从而可以在对数的时间复杂度下访问序列上的任意一个
区间并进行维护。在本章中,将学习线段树的构建方法和一些简单的应用。
引言
线段树,它将一段区间划分为若干 单位区间
,每一个节点都储存着一个区间。它 功能强大
,支持区间求和,区间最大值,区间修改,单点修改等操作。它的大致思想是:将一段大区间平均地划分成
2个小区间,每一个小区间都再平均分成 2
个更小区间……以此类推,直到每一个区间的 L 等于 R
(这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。
可以用线段树维护的问题必须满足 区间加法
,否则是不可能将大问题划分成子问题来解决的。
例如:
符合区间加法的例子:
数字之和——总数字之和 = 左区间数字之和 + 右区间数字之和
最大公因数(GCD)——总GCD = gcd( 左区间GCD , 右区间GCD );
最大值——总最大值=max(左区间最大值,右区间最大值)
不符合区间加法的例子:
众数——只知道左右区间的众数,没法求总区间的众数
01序列的最长连续零——只知道左右区间的最长连续零,没法知道总的最长连续零
辨析:区间[L,R]指的是下标从L到R的这(R-L+1)个数,而不是指一条连续的线段。
线段树是将每个区间[L,R]分解成[L,M]和[M+1,R] (其中M=(L+R)/2
这里的除法是整数除法,即对结果下取整)
由于树的天然结构,线段树天生可以通过递归来逐步分解。
img
上图中,每个区间都是一个节点,每个节点存自己对应的区间的统计信息。
线段树的点修改:
每层只有一个节点包含我们要修改的节点,因此每层只需要更新一个节点即可,复杂度是(logn)

区间修改
使用懒标记后,对于那些正好是线段树节点的区间,我们不继续递归下去,而是打上一个懒标记,将来要用到它的子区间的时候,再向下传递。
更新时,我们是从最大的区间开始,递归向下处理。
pushdown操作
它可以把懒标记下传。
设想一下,如果我们要把懒标记下传,应该注意什么呢?
首先,要给子节点打上懒标记。
然后,我们要修改子节点上的值。
最后,不要忘记把这个节点的懒标记清空。
详见代码
区间查询
有了区间修改的经验,区间查询的方法完全类似。
接下来看模板罢
【模板】线段树 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
- 将某区间每一个数加上 \(k\)。
- 求出某区间每一个数的和。
输入格式
第一行包含两个整数 \(n,
m\),分别表示该数列数字的个数和操作的总个数。
第二行包含 \(n\)
个用空格分隔的整数,其中第 \(i\)
个数字表示数列第 \(i\) 项的初始值。
接下来 \(m\) 行每行包含 \(3\) 或 \(4\) 个整数,表示一个操作,具体如下:
1 x y k
:将区间 \([x,
y]\) 内每个数加上 \(k\)。
2 x y
:输出区间 \([x,
y]\) 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
样例 #1
样例输入 #1
1 2 3 4 5 6 7
| 5 5 1 5 4 2 3 2 2 4 1 2 3 2 2 3 4 1 1 5 1 2 1 4
|
样例输出 #1
提示
对于 \(30\%\) 的数据:\(n \le 8\),\(m
\le 10\)。
对于 \(70\%\) 的数据:\(n \le {10}^3\),\(m \le {10}^4\)。
对于 \(100\%\) 的数据:\(1 \le n, m \le {10}^5\)。
保证任意时刻数列中所有元素的绝对值之和 \(\le {10}^{18}\)。
【样例解释】

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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
| #include<bits/stdc++.h> using namespace std; using ll=long long; const ll N=1e6; #define rep(i,a,n) for(int i=a;i<=n;i++) ll a[N]; ll w[N*4]; void pushup(const int u) { w[u]=w[u*2]+w[u*2+1]; } void build(const int u,int l,int r) { if(l==r) { w[u]=a[l]; return ; } int mid=(l+r)/2; build(u*2,l,mid); build(u*2+1,mid+1,r); pushup(u); }
ll query1(int u,int l,int r,int p) {
if(l==r) { return w[u]; } else { int mid=(l+r)/2; if(mid>=p) { return query1(u*2,l,mid,p); } else
{ return query1(u*2+1,mid+1,r,p); } }
}
ll update1(int u,int l,int r,int p,ll x) { if(l==r) { w[u]=x; } else { int mid=(l+r)>>1; if(mid>=p) { update1(u*2,l,mid,p,x); } else { update1(u*2+1,mid+1,r,p,x); } pushup(u); } }
bool inrange(int L,int R,int l,int r) { return (l<=L)&&(R<=r); }
bool outrange(int L,int R,int l,int r) { return (L>r)||(R<l); }
ll lzy[N*4];
void maketag(int u,int len,ll x) { lzy[u]+=x; w[u]+=len*x; }
void pushdown(int u,int L,int R) { int Mid=(L+R)/2; maketag(u*2,Mid-L+1,lzy[u]); maketag(u*2+1,R-Mid,lzy[u]); lzy[u]=0; } ll query(int u,int L,int R,int l,int r) { if(inrange(L,R,l,r)) { return w[u]; } else if(!outrange(L,R,l,r)) { int Mid=(L+R)/2; pushdown(u,L,R); return query(u*2,L,Mid,l,r)+query(u*2+1,Mid+1,R,l,r); } else { return 0; } } void update(int u,int L,int R,int l,int r,ll x) { if(inrange(L,R,l,r)) { maketag(u,R-L+1,x); } else if(!outrange(L,R,l,r)) { int Mid=(L+R)/2; pushdown(u,L,R); update(u*2,L,Mid,l,r,x); update(u*2+1,Mid+1,R,l,r,x); pushup(u); } } int main() { int n,m; cin>>n>>m; rep(i,1,n) { cin>>a[i]; } build(1,1,n); rep(i,1,m) { int op,x,y; cin>>op; ll k; if(op==1) { cin>>x>>y>>k; update(1,1,n,x,y,k); } else { cin>>x>>y; cout<<(query(1,1,n,x,y))<<'\n'; } } }
|
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
| #include <bits/stdc++.h> using namespace std; const int N=1e5+10; struct node{ int sum; int l,r; int add; }tr[N*4]; int a[N]; inline void pushup(int x){ tr[x].sum=tr[2*x].sum+tr[2*x+1].sum; } inline void pushudown(int x){ if(tr[x].add){ tr[2*x].add+=tr[x].add,tr[2*x+1].add+=tr[x].add; tr[2*x].sum+=tr[x].add*(tr[2*x].r-tr[2*x].l+1); tr[2*x+1].sum+=tr[x].add*(tr[2*x+1].r-tr[2*x+1].l+1); tr[x].add=0; } } void build(int x,int l,int r){ tr[x].l=l,tr[x].r=r,tr[x].add=0; if(l==r){ tr[x].sum=a[l]; return; } int mid=(l+r)/2; build(2*x,l,mid),build(2*x+1,mid+1,r); pushup(x); } int query(int x,int l,int r){ if(l<=tr[x].l&&r>=tr[x].r) return tr[x].sum; pushudown(x); int mid=(tr[x].l+tr[x].r)/2,sum=0; if(l<=mid) sum+=query(x*2,l,r); if(r>mid) sum+=query(x*2+1,l,r); return sum; } void update(int now,int l,int r,int k){ if(l<=tr[now].l&&r>=tr[now].r){ tr[now].sum+=k*(tr[now].r-tr[now].l+1); tr[now].add+=k; } else{ pushudown(now); int mid=(tr[now].l+tr[now].r)/2; if(l<=mid) update(now*2,l,r,k); if(r>mid) update(now*2+1,l,r,k); pushup(now); } } int n,q; int main(){ cin>>n>>q; for(int i=1;i<=n;i++) cin>>a[i]; build(1,1,n); while(q--){ int l,r,k,c; cin>>c>>l>>r; if(c==1){ cin>>k; update(1,l,r,k); } else cout<<query(1,l,r)<<endl; } return 0; }
|
结构体存储:(相信看完前面的你应该能理解)
用这个模板ac以下树状数组:
【模板】树状数组 1
题目描述
如题,已知一个数列,你需要进行下面两种操作:
将某一个数加上 \(x\)
求出某区间每一个数的和
输入格式
第一行包含两个正整数 \(n,m\),分别表示该数列数字的个数和操作的总个数。
第二行包含 \(n\)
个用空格分隔的整数,其中第 \(i\)
个数字表示数列第 \(i\) 项的初始值。
接下来 \(m\) 行每行包含 \(3\) 个整数,表示一个操作,具体如下:
输出格式
输出包含若干行整数,即为所有操作 \(2\) 的结果。
样例 #1
样例输入 #1
1 2 3 4 5 6 7
| 5 5 1 5 4 2 3 1 1 3 2 2 5 1 3 -1 1 4 2 2 1 4
|
样例输出 #1
提示
【数据范围】
对于 \(30\%\) 的数据,\(1 \le n \le 8\),\(1\le m \le 10\);
对于 \(70\%\) 的数据,\(1\le n,m \le 10^4\);
对于 \(100\%\) 的数据,\(1\le n,m \le 5\times 10^5\)。
数据保证对于任意时刻,\(a\)
的任意子区间(包括长度为 \(1\) 和 \(n\) 的子区间)和均在 \([-2^{31}, 2^{31})\) 范围内。
样例说明:

故输出结果14、16
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
| #include <bits/stdc++.h> using namespace std; const int N=1e6+10; struct node{ int sum,l,r; }tr[N*4]; int a[N]; inline void pushup(int x){ tr[x].sum=tr[x*2].sum+tr[x*2+1].sum; } void build(int x,int l,int r){ tr[x].l=l,tr[x].r=r; if(l==r){ tr[x].sum=a[l]; return; } int mid=(l+r)/2; build(x*2,l,mid),build(x*2+1,mid+1,r); pushup(x); } int query(int x,int l,int r){ if(tr[x].l>=l&&tr[x].r<=r) return tr[x].sum; int mid=(tr[x].l+tr[x].r)/2; int sum=0; if(l<=mid) sum+=query(x*2,l,r); if(r>mid) sum+=query(x*2+1,l,r); return sum; } void change(int now,int x,int k){ if(tr[now].l==tr[now].r) { tr[now].sum+=k; return; } int mid=(tr[now].l+tr[now].r)/2; if(x<=mid) change(now*2,x,k); else change(now*2+1,x,k); pushup(now); }
int n,q; int main(){ cin>>n>>q; for(int i=1;i<=n;i++) cin>>a[i]; build(1,1,n); while(q--){ int t,b,c; cin>>t>>b>>c; if(t==1) change(1,b,c); else cout<<query(1,b,c)<<endl; } }
|
【模板】线段树 2
题目描述
如题,已知一个数列,你需要进行下面三种操作:
- 将某区间每一个数乘上 \(x\);
- 将某区间每一个数加上 \(x\);
- 求出某区间每一个数的和。
输入格式
第一行包含三个整数 \(n,q,m\),分别表示该数列数字的个数、操作的总个数和模数。
第二行包含 \(n\)
个用空格分隔的整数,其中第 \(i\)
个数字表示数列第 \(i\) 项的初始值。
接下来 \(q\)
行每行包含若干个整数,表示一个操作,具体如下:
操作 \(1\):
格式:1 x y k
含义:将区间 \([x,y]\) 内每个数乘上 \(k\)
操作 \(2\):
格式:2 x y k
含义:将区间 \([x,y]\) 内每个数加上 \(k\)
操作 \(3\):
格式:3 x y
含义:输出区间 \([x,y]\) 内每个数的和对 \(m\) 取模所得的结果
输出格式
输出包含若干行整数,即为所有操作 \(3\) 的结果。
样例 #1
样例输入 #1
1 2 3 4 5 6 7
| 5 5 38 1 5 4 2 3 2 1 4 1 3 2 5 1 2 4 2 2 3 5 5 3 1 4
|
样例输出 #1
提示
【数据范围】
对于 \(30\%\) 的数据:\(n \le 8\),\(q
\le 10\)。
对于 \(70\%\) 的数据:$n ^3 \(,\)q ^4$。
对于 \(100\%\) 的数据:\(1 \le n \le 10^5\),\(1 \le q \le 10^5\)。
除样例外,\(m = 571373\)。
(数据已经过加强 _)
样例说明:

故输出应为 \(17\)、\(2\)(\(40 \bmod
38 = 2\))。
本题核心是懒标记的如何处理
很明显乘除都有懒标记
我们该怎样安排懒标记的pushdown
顺序呢?
我们考虑 先乘后加 的维护顺序。假设两个懒标记分别是 $
$ 和 $ $,那么一个数值的更新公式可以表示为:
$ = + . $
更新规则
**加上一个新的 $ \(:**
新的值会变成:\) = + + {}. $ 这里的 $ {} $
是新增的加法操作。
**乘上一个新的 $ \(:**
新的值会变成:\) = ( {}) + ( {}). $
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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
| #include<bits/stdc++.h> using namespace std; #define rep(i, a, n) for (int i = a; i <= n; i++)
using ll=long long; const int N=1e6+10; int p; ll a[N]; ll w[N<<2]; ll lzy_add[N<<2]; ll lzy_mul[N<<2]; void pushup(const int u) { w[u]=(w[u<<1]+w[u<<1|1])%p; } void build(const int u,int l,int r) { lzy_mul[u]=1; if(l==r) { w[u]=a[l]; return ; } int mid=(l+r)>>1; build(u<<1,l,mid); build(u<<1|1,mid+1,r); pushup(u); } bool Inrange(int L,int R,int l,int r) { return (l<=L&&r>=R); } bool outrange(int L,int R,int l,int r) { return (L>r)||(R<l); } void maketag(int u,int L,int R,ll x ,int type) { if(type==1) { (lzy_add[u]*=x)%=p; (lzy_mul[u]*=x)%=p; (w[u]*=x)%=p; } else { (lzy_add[u]+=x)%=p; (w[u]+=(R-L+1)*x)%=p; } } void pushdown(int u,int L,int R) { int Mid=(L+R)/2; maketag(u<<1,L,Mid,lzy_mul[u],1); maketag(u << 1, L, Mid, lzy_add[u], 2); maketag(u << 1|1, Mid+1,R, lzy_mul[u], 1); maketag(u<<1|1,Mid+1,R,lzy_add[u],2); lzy_mul[u]=1; lzy_add[u]=0; } ll query(int u, int L, int R, int l, int r) { if (Inrange(L, R, l, r)) { return w[u]%p; } else if (!outrange(L, R, l, r)) { int Mid = (L + R) / 2; pushdown(u, L, R); return (((query(u * 2, L, Mid, l, r))%p) + ((query(u * 2 + 1, Mid + 1, R, l, r))%p))%p; } else { return 0; } } void update(int u, int L, int R, int l, int r, ll x,int type) { if (Inrange(L, R, l, r)) { maketag(u, L,R, x,type); } else if (!outrange(L, R, l, r)) { int Mid = (L + R) / 2; pushdown(u, L, R); update(u <<1, L, Mid, l, r, x,type); update(u<<1|1, Mid + 1, R, l, r, x,type); pushup(u); } } int main() { std::ios::sync_with_stdio(false); std::cin.tie(0); std::cout.tie(0); int n, m; cin >> n >> m>>p; rep(i, 1, n) { cin >> a[i]; } build(1,1,n); int opt; rep(i,1,m) { cin>>opt; if(opt==1) { int x,y; ll z;
cin>>x>>y>>z; update(1,1,n,x,y,z,1); } else if(opt==2) { int x, y; ll z;
cin >> x >> y >> z; update(1, 1, n, x, y, z, 2); } else { int x,y; cin>>x>>y; cout<<query(1,1,n,x,y)%p<<'\n'; } } }
|
结构体版本+注释
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
| #include <bits/stdc++.h> using namespace std; const int N=1e5+10; struct node{ int l,r; int sum,add,mul; }tr[N*4]; int a[N]; int n,p,m; inline void pushup(int x){ tr[x].sum=(tr[2*x].sum+tr[2*x+1].sum)%p; } inline void eval(int x,int add,int mul){ tr[x].sum=(tr[x].sum*mul+add*(tr[x].r-tr[x].l+1))%p; tr[x].mul=(mul*tr[x].mul)%p; tr[x].add=(tr[x].add*mul+add)%p; }
inline void pushdown(int x){ eval(x*2,tr[x].add,tr[x].mul); eval(x*2+1,tr[x].add,tr[x].mul); tr[x].add=0,tr[x].mul=1; } void build(int x,int l,int r){ tr[x].l=l,tr[x].r=r; tr[x].add=0,tr[x].mul=1; if(l==r){ tr[x].sum=a[l]; return; } int mid=(l+r)/2; build(x*2,l,mid),build(x*2+1,mid+1,r); pushup(x); } void change(int x,int l,int r,int add,int mul){ if(l<=tr[x].l&&r>=tr[x].r) eval(x,add,mul); else{ pushdown(x); int mid=(tr[x].l+tr[x].r)/2; if(l<=mid) change(x*2,l,r,add,mul); if(r>mid) change(x*2+1,l,r,add,mul); pushup(x); } } int query(int x,int l,int r){ if(l<=tr[x].l&&r>=tr[x].r) return tr[x].sum; int sum=0; pushdown(x); int mid=(tr[x].l+tr[x].r)/2; if(l<=mid) sum+=query(x*2,l,r)%p; if(r>mid) sum+=query(x*2+1,l,r)%p; return sum; } int main(){ int t,g,c,ch; cin>>n>>m>>p; for(int i=1;i<=n;i++) cin>>a[i]; build(1,1,n); while(m--){ cin>>ch>>t>>g; if(ch==1){ cin>>c; change(1,t,g,0,c); } else if(ch==2){ cin>>c; change(1,t,g,c,1); } else cout<<query(1,t,g)%p<<endl; } return 0; }
|
介绍一个知识点:标记永久化:
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
| const int N=1e4+10; struct node{ int sum,add; int l,r; }tr[N*4]; int a[N]; void build(int x,int l,int r){ tr[x].l=l,tr[x].r=r; if(l==r){ tr[x].sum=a[l],tr[x].add=0; return; } int mid=(l+r)/2; build(x*2,l,mid),build(x*2+1,mid+1,r); tr[x].sum=tr[x*2].sum+tr[x*2+1].sum; } void update(int x,int l,int r,int k) { tr[x].sum+=(min(tr[x].r,r)-max(tr[x].l,l)+1)*k; if(tr[x].l>=l&&tr[x].r<=r){ tr[x].add+=k; return; } int mid=(tr[x].l+tr[x].r)/2; if(l<=mid) update(x*2,l,r,k); if(r>mid) update(x*2+1,l,r,k); } int query(int x,int l,int r,int add){ if(tr[x].l>=l&&tr[x].r<=r){ int s=(tr[x].r-tr[x].l+1)*add; return tr[x].sum+s; } add+=tr[x].add; int mid=(tr[x].l+tr[x].r)/2,sum=0; if(l<=mid) sum+=query(x*2,l,r,add); if(r>mid) sum+=query(x*2+1,l,r,add); return sum; }
|
权值线段树
权值线段树与线段树的不同点在于,线段树维护区间信息,权值线段树维护值域信息。
如图,权值线段树就长这个样子。
在这里插入图片描述
现在我们插入一个数44。
在这里插入图片描述
每一个包含44的区间都被加上了1。
那么每个区间维护的到底是什么呢?
是这个区间内的数的数量。
这就是权值线段树的原理。
权值线段树可以干很多事情,比如查询第�k小,查找前驱后继等。
模板:
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
| int tr[N*4];
inline void pushup(int x){ tr[x]=tr[x*2]+tr[x*2+1]; } void insert(int x,int l,int r,int k){ if(l==r){ tr[x]++; return; } int mid=(l+r)/2; if(k<=mid) insert(x*2,l,mid,k); else insert(x*2+1,mid+1,r,k); pushup(x); } void del(int x,int l,int r,int k){ if(l==r){ tr[x]--; return; } int mid=(l+r)/2; if(k<=mid) del(x*2,l,mid,k); else del(x*2+1,mid+1,r,k); pushup(x); } int query(int x,int l,int r,int ql,int qr){ if(l>=ql&&r<=qr) return tr[x]; int mid=(l+r)/2,sum=0; if(ql<=mid) sum=query(x*2,l,mid,ql,qr); if(qr>mid) sum+=query(x*2+1,mid+1,r,ql,qr); return sum; }
|
NK 中学组织同学们去五云山寨参加社会实践活动,按惯例要乘坐火车去。由于
NK 中学的学生很多,在火车开之前必须清点好人数。
初始时,火车上没有学生。当同学们开始上火车时,年级主任从第一节车厢出发走到最后一节车厢,每节车厢随时都有可能有同学上下。年级主任走到第m节车厢时,他想知道前m*节车厢上一共有多少学生。每次提问,m
总会比前一次大。
题目分析
很明显可以用权值线段树做,维护每个区间的数的数量,具体见代码。
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
| #include <bits/stdc++.h> using namespace std; const int N=5e5+10; int tr[N*4]; inline void pushup(int x){ tr[x]=tr[x*2]+tr[x*2+1]; } void insert(int x,int l,int r,int k,int p){ if(l==r){ tr[x]+=p; return; } int mid=(l+r)/2; if(k<=mid) insert(x*2,l,mid,k,p); else insert(x*2+1,mid+1,r,k,p); pushup(x); } int query(int x,int l,int r,int ql,int qr){ if(l>=ql&&r<=qr) return tr[x]; int mid=(l+r)/2,sum=0; if(ql<=mid) sum=query(x*2,l,mid,ql,qr); if(qr>mid) sum+=query(x*2+1,mid+1,r,ql,qr); return sum; } int n,k; int main(){ cin>>n>>k; while(k--){ char opt; int m,p; cin>>opt; if(opt=='A'){ cin>>m; cout<<query(1,1,n,1,m)<<endl; } else if(opt=='B'){ cin>>m>>p; insert(1,1,n,m,p); } else{ cin>>m>>p; insert(1,1,n,m,-p); } } }
|
查询第k大数
$$ 如果
k小于区间中点,那么也就说明结果为左区间第
k大数。否则,也就说明结果为右区间第
k−l_size_ 大数。 $$
1 2 3 4 5 6
| int kth(int x,int l,int r,int k){ if(l==r) return l; int mid=(l+r)/2; if(k<=tr[x*2]) return kth(x*2,l,mid,k); C++ return kth(x*2+1,mid+1,r,k-tr[x*2]); }
|
查询一个数的排名
每次把x与当前区间中点mid比较,如果小于等于mid,说明在左区间,向左儿子寻找。
如果大于mid,说明在右区间,这时,它的排名要加上左子树的大小(它比整个左子树的数都大)
1 2 3 4 5 6
| int rnk(int x,int l,int r,int k){ if(l==r) return 1; int mid=(l+r)/2; if(k<=mid) return rnk(x*2,l,mid,k); return rnk(x*2+1,mid+1,r,k)+tr[x*2]; }
|
动态开点代码解决平衡树
【模板】普通平衡树
题目描述
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入一个数 \(x\)。
- 删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
- 定义排名为比当前数小的数的个数 \(+1\)。查询 \(x\) 的排名。
- 查询数据结构中排名为 \(x\)
的数。
- 求 \(x\) 的前驱(前驱定义为小于
\(x\),且最大的数)。
- 求 \(x\) 的后继(后继定义为大于
\(x\),且最小的数)。
对于操作 3,5,6,不保证当前数据结构中存在数 \(x\)。
输入格式
第一行为 \(n\),表示操作的个数,下面
\(n\) 行每行有两个数 \(\text{opt}\) 和 \(x\),\(\text{opt}\) 表示操作的序号($ 1 $)
输出格式
对于操作 \(3,4,5,6\)
每行输出一个数,表示对应答案。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 8 9 10 11
| 10 1 106465 4 1 1 317721 1 460929 1 644985 1 84185 1 89851 6 81968 1 492737 5 493598
|
样例输出 #1
提示
【数据范围】
对于 \(100\%\) 的数据,\(1\le n \le 10^5\),\(|x| \le 10^7\)
来源:Tyvj1728 原名:普通平衡树
在此鸣谢
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
| #include <bits/stdc++.h> using namespace std; const int N=1e7+10,M=4e5+10; int tr[M]; int ls[M],rs[M],tot=0; inline void pushup(int x) { tr[x]=tr[ls[x]]+tr[rs[x]]; } void insert(int &x,int l,int r,int k,int p){ if(!x) x=++tot; if(l==r) { tr[x]+=p; return; } int mid=(l+r)/2; if(k<=mid) insert(ls[x],l,mid,k,p); else insert(rs[x],mid+1,r,k,p); pushup(x); } int kth(int x,int l,int r,int k){ if(l==r) return l; int mid=(l+r)/2; if(k<=tr[ls[x]]) return kth(ls[x],l,mid,k); return kth(rs[x],mid+1,r,k-tr[ls[x]]); } int rnk(int x,int l,int r,int k){ if(l==r) return 1; int mid=(l+r)/2; if(k<=mid) return rnk(ls[x],l,mid,k); return rnk(rs[x],mid+1,r,k)+tr[ls[x]]; } int n,root; int main(){ cin>>n; while(n--){ int opt,x; cin>>opt>>x; switch(opt){ case 1: insert(root,-N,N,x,1); break; case 2: insert(root,-N,N,x,-1); break; case 3: cout<<rnk(root,-N,N,x)<<endl; break; case 4: cout<<kth(root,-N,N,x)<<endl; break; case 5: cout<<kth(root,-N,N,rnk(root,-N,N,x)-1)<<endl; break; case 6: cout<<kth(root,-N,N,rnk(root,-N,N,x+1))<<endl; break; } } }
|
ZWK线段树
循环实现
两倍空间奇效
普通的线段树,是从上到下处理的。因为对于普通线段树,我们很容易定位根节点,却不容易定位叶子节点。
建树代码
1 2 3 4 5 6 7
| int tr[N*4]; int n,m; void build(){ cin>>n>>m; for(int i=n+1;i<=2*n;i++) cin>>tr[i]; for(int i=n-1;i>=1;i--) tr[i]=tr[i*2]+tr[i*2+1]; }
|
单点修改代码
1 2 3
| inline void change(int x,int k){ for(int i=x+=n;i;i/=2) tr[i]+=k; }
|
单点查询代码
1 2 3
| inline int query_one(int x){ return tr[x+n]; }
|
区间查询代码
1 2 3 4 5 6 7 8
| inline int query(int l,int r){ int ans=0; for(int p=l-1,q=r+1;p/2!=q/2;p/=2,q/=2){ if(!(p%2)) ans+=tr[p+1]; if(q%2) ans+=tr[q-1]; } return ans; }
|
区间修改代码
1 2 3 4 5 6
| void uplate(int l,int r,int k){ for(int p=l-1,q=r+1;p/2!=q/2;p/=2,q/=2){ if(!(p%2)) add[p+1]+=k; if(q%2) add[q-1]+=k; } }
|
单点查询
在有懒标记的情况下,单点查询也变得不同。
首先自底向上累加所有祖宗节点的懒标记,然后再加上本身的值。
单点查询代码
1 2 3 4 5
| inline int query_one(int x){ int sum=0; for(int i=x+=n;i;i/=2) sum+=add[i]; return tr[x+n]+add[i]; }
|
可持久化线段树
复用历史版本的节点
核心代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void build(int &x,int l,int r){ x=++tot; if(l==r){ tr[x]=a[l]; return; } int mid=(l+r)/2; build(ls[x],l,mid),build(rs[x],mid+1,r); } void change(int u,int &x,int l,int r,int k,int p){ x=++tot; tr[x]=tr[u],ls[x]=ls[u],rs[x]=rs[u]; if(l==r){ tr[x]=p; return; } int mid=(l+r)/2; if(k<=mid) change(ls[u],ls[x],l,mid,k,p); else change(rs[u],rs[x],mid+1,r,k,p); }
|