线段树小结 A Summary for Segment Tree
0. Anouncement
本文部分图片以及部分内容来自互联网,内容过多就不一一注明出处了,冒犯之处还请海涵。
Some of the pictures and the content of the text come from the Internet.
Due to plenty of the content,there will be no quotation. If offended, please try to forgive me.
Ⅰ. 线段树
什么是线段树?
首先我们来看一个图, 线段树就是每个节点维护一个区间
的树。
可以常数倍空间复杂度的情况下高效的 维 护信息(维护一般指的是更新和查询) 。
本文除特别题目外,区间以及根节点的下标均从 1 开始,线段树的维护信息均以数组的方法存储。
线段树的时空复杂度
由上图我们可以发现: 线段树维护的区间在同一高度上是完全不相交的,并且恰好包含了所有的区间。
图中红字所表示的是每个节点的所在的数组的下标
我们可以发现每个节点的左儿子是父亲节点下标 * 2
,右儿子是父亲节点下标 * 2 + 1
并且我们也发现以这样的存储方式,仅仅开2倍的空间是不够的,4倍才是合适的
至于为什么是4倍?详情可以看这里,点击查看
由于线段树的二叉树形结构,更新和查询的时间复杂度操作都是O(logn)
Ⅱ. 线段树的代码模版
l,r,rt: 保存的区间信息,其中rt是这个区间信息所存的节点的下标
L,R: 每次操作要更新的区间
v: 更新的值
HH风格的线段树,感谢HH大牛对于线段树学习的分享单点更新 区间查询(单点查询其实就是区间查询的特例)
push_up 把儿子节点的信息更新到自己
//以单点更新 区间查询的RMQ为例
const int N = 1e5 + 10;
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
int maxv[N << 2];
void push_up(int rt) {
maxv[rt] = max(maxv[rt << 1], maxv[rt << 1 | 1]);
}
void build(int l, int r, int rt) {
if(l == r) {
scanf("%d", &maxv[rt]);
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
push_up(rt);
}
void update(int o, int v, int l, int r, int rt) {
if(l == r) {
maxv[rt] = v;
return;
}
int m = l + r >> 1;
if(o <= m) update(o, v, lson);
else update(o, v, rson);
push_up(rt);
}
int query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) return maxv[rt];
int m = l + r >> 1;
int ret = -INF;
if(L <= m) ret = max(ret, query(L, R, lson));
if(R > m) ret = max(ret, query(L, R, rson));
return ret;
}
- 区间更新 区间查询
push_down 在更新或者查询到当前节点的左右儿子节点时,提前更新它的左右儿子节点,
这就是懒惰操作lazy propagation
这是一个坎,建议不要去看什么网上的讲解,代码就是最好的讲解,模拟几遍代码就懂了
//以区间更新 区间求和为例
const int N = 1e5 + 10;
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
int sum[N << 2], add[N << 2];
void push_up(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void push_down(int rt, int m) {
if(add[rt]) {
sum[rt << 1] += add[rt] * (m - (m >> 1));
sum[rt << 1 | 1] += add[rt] * (m >> 1);
add[rt << 1] += add[rt];
add[rt << 1 | 1] += add[rt];
add[rt] = 0;
}
}
void update(int L, int R, int v, int l, int r, int rt) {
if(L <= l && r <= R) {
add[rt] += v;
sum[rt] += v * (r - l + 1);
return;
}
push_down(rt, r - l + 1);
int m = l + r >> 1;
if(L <= m) update(L, R, v, lson);
if(R > m) update(L, R, v, rson);
push_up(rt);
}
int query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) return sum[rt];
push_down(rt, r - l + 1);
int m = l + r >> 1, ret = 0;
if(L <= m) ret += query(L, R, lson);
if(R > m) ret += query(L, R, rson);
return ret;
}
Ⅲ. 线段树的题目讲解
所有的题目不外乎都是这两种模型,但是题目也有一定的技巧,即使是区间更新也不一定要往下传递lazy标记,
我们将以具体的题目配合进行讲解
1. 单点更新 区间查询
- HDU 1166 敌兵布阵
update:单点增减 query:区间求和
//
// Created by TaoSama on 2015-05-19
// Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 5e4 + 10;
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
int n, sum[N << 2];
void push_up(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void build(int l, int r, int rt) {
if(l == r) {
scanf("%d", &sum[rt]);
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
push_up(rt);
}
void update(int o, int v, int l, int r, int rt) {
if(l == r) {
sum[rt] += v;
return;
}
int m = l + r >> 1;
if(o <= m) update(o, v, lson);
else update(o, v, rson);
push_up(rt);
}
int query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) return sum[rt];
int m = l + r >> 1;
int ret = 0;
if(L <= m) ret += query(L, R, lson);
if(R > m) ret += query(L, R, rson);
return ret;
}
int main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
int t; scanf("%d", &t);
int kase = 0;
while(t--) {
printf("Case %d:\n", ++kase);
scanf("%d", &n);
build(root);
char op[10]; int x, y;
while(scanf("%s", op)) {
if(op[0] == 'E') break;
scanf("%d%d", &x, &y);
if(op[0] == 'Q') printf("%d\n", query(x, y, root));
else if(op[0] == 'A') update(x, y, root);
else if(op[0] == 'S') update(x, -y, root);
}
}
return 0;
}
- HDU 1754 I Hate It
update:单点更新 query:区间最值
//
// Created by TaoSama on 2015-05-19
// Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 2e5 + 10;
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
int n, q, maxv[N << 2];
void push_up(int rt) {
maxv[rt] = max(maxv[rt << 1], maxv[rt << 1 | 1]);
}
void build(int l, int r, int rt) {
if(l == r) {
scanf("%d", &maxv[rt]);
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
push_up(rt);
}
void update(int o, int v, int l, int r, int rt) {
if(l == r) {
maxv[rt] = v;
return;
}
int m = l + r >> 1;
if(o <= m) update(o, v, lson);
else update(o, v, rson);
push_up(rt);
}
int query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) return maxv[rt];
int m = l + r >> 1;
int ret = -INF;
if(L <= m) ret = max(ret, query(L, R, lson));
if(R > m) ret = max(ret, query(L, R, rson));
return ret;
}
int main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
while(scanf("%d%d", &n, &q) == 2) {
build(root);
while(q--) {
char op[2]; int x, y;
scanf("%s%d%d", op, &x, &y);
if(op[0] == 'Q') printf("%d\n", query(x, y, root));
else update(x, y, root);
}
}
return 0;
}
- POJ 3264 Balanced Lineup
query:区间最值
//
// Created by TaoSama on 2015-05-21
// Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 5e4 + 10;
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
int n, q, maxv[N << 2], minv[N << 2];
void push_up(int rt) {
maxv[rt] = max(maxv[rt << 1], maxv[rt << 1 | 1]);
minv[rt] = min(minv[rt << 1], minv[rt << 1 | 1]);
}
void build(int l, int r, int rt) {
if(l == r) {
int x; scanf("%d", &x);
maxv[rt] = minv[rt] = x;
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
push_up(rt);
}
int query(int op, int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) {
return op ? maxv[rt] : minv[rt];
}
int Max = -INF, Min = INF;
int m = l + r >> 1;
if(op) {
if(L <= m) Max = max(Max, query(op, L, R, lson));
if(R > m) Max = max(Max, query(op, L, R, rson));
} else {
if(L <= m) Min = min(Min, query(op, L, R, lson));
if(R > m) Min = min(Min, query(op, L, R, rson));
}
return op ? Max : Min;
}
int main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
while(scanf("%d%d", &n, &q) == 2) {
build(root);
while(q--) {
int x, y; scanf("%d%d", &x, &y);
int Max = query(1, x, y, root);
int Min = query(0, x, y, root);
printf("%d\n", Max - Min);
}
}
return 0;
}
2. 区间更新 区间查询
- HDU 1698 Just a Hook
update:区间更新
//
// Created by TaoSama on 2015-05-21
// Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
int n, q, sum[N << 2], row[N << 2];
void push_up(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void push_down(int rt, int m) {
if(row[rt]) {
sum[rt << 1] = row[rt] * (m - (m >> 1));
sum[rt << 1 | 1] = row[rt] * (m >> 1);
row[rt << 1] = row[rt << 1 | 1] = row[rt];
row[rt] = 0;
}
}
void build(int l, int r, int rt) {
row[rt] = 0;
if(l == r) {
sum[rt] = 1;
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
push_up(rt);
}
void update(int L, int R, int v, int l, int r, int rt) {
if(L <= l && r <= R) {
sum[rt] = v * (r - l + 1);
row[rt] = v;
return;
}
push_down(rt, r - l + 1);
int m = l + r >> 1;
if(L <= m) update(L, R, v, lson);
if(R > m) update(L, R, v, rson);
push_up(rt);
}
int main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
int t; scanf("%d", &t);
int kase = 0;
while(t--) {
scanf("%d%d", &n, &q);
build(root);
while(q--) {
int x, y, z; scanf("%d%d%d", &x, &y, &z);
update(x, y, z, root);
}
printf("Case %d: The total value of the hook is %d.\n",
++kase, sum[1]);
}
return 0;
}
- POJ 3486 A Simple Problem with Integers
update:区间更新 query:区间求和
//
// Created by TaoSama on 2015-05-19
// Copyright (c) 2015 TaoSama. All rights reserved.
//
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;
#define root 1, n, 1
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
typedef long long LL;
int n, q;
LL sum[N << 2], add[N << 2];
void push_up(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
}
void push_down(int rt, int m) {
if(add[rt]) {
add[rt << 1] += add[rt];
add[rt << 1 | 1] += add[rt];
sum[rt << 1] += add[rt] * (m - (m >> 1));
sum[rt << 1 | 1] += add[rt] * (m >> 1);
add[rt] = 0;
}
}
void build(int l, int r, int rt) {
add[rt] = 0;
if(l == r) {
scanf("%lld", &sum[rt]);
return;
}
int m = l + r >> 1;
build(lson);
build(rson);
push_up(rt);
}
void update(int L, int R, int v, int l, int r, int rt) {
if(L <= l && r <= R) {
add[rt] += v;
sum[rt] += (LL)v * (r - l + 1);
return;
}
push_down(rt, r - l + 1);
int m = l + r >> 1;
if(L <= m) update(L, R, v, lson);
if(R > m)update(L, R, v, rson);
push_up(rt);
}
LL query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) return sum[rt];
push_down(rt, r - l + 1);
int m = l + r >> 1;
LL ret = 0;
if(L <= m) ret += query(L, R, lson);
if(R > m) ret += query(L, R, rson);
return ret;
}
int main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
ios_base::sync_with_stdio(0);
while(scanf("%d%d", &n, &q) == 2) {
build(root);
while(q--) {
char op[2]; int x, y, z;
scanf("%s%d%d", op, &x, &y);
if(op[0] == 'Q') printf("%lld\n", query(x, y, root));
else {
scanf("%d", &z);
update(x, y, z, root);
}
}
}
return 0;
}
- POJ 2528 Mayor’s posters
update:区间更新 query:查询所有节点
- HDU 4027 Can you answer these queries?
Tip: 多次开方后会变成1
update:区间更新 query:区间查询
- ZOJ 1610 Count the Colors
update:区间染色(技巧) query:单点查询
3. 线段树区间合并
- HDU 1540 Tunnel Warfare
这类题目会询问区间中满足条件的连续最长区间,经典的维护技巧
4. dfs序维护区间线段树
- HDU 3974 Assign the task
维护树的区间 可以使用dfs时间戳
update:区间更新 query:单点查询
5. 扫描线与线段树
HDU 1542 Atlantis
矩形面积并 不用传递懒惰标记 模拟一下就懂了
update:区间更新POJ 1177 Picture
矩形周长并 与上面那个题相似 两种做法
第一种直接横着扫描一次再竖着扫描一次
第二种在横着扫描的时候再同时维护 竖线的个数
update:单点增减 query:区间求和HDU 1255 覆盖的面积
前面的与矩形面积并类似, 不同的是push_up的时候要考虑至少覆盖一次one和至少覆盖两次two的更新
update:区间更新
6. 线段树求第K大(其实就是在线段树上二分)
7. 高维线段树与多颗线段树
- Uva 11992 Fast Matrix Operations
二维线段树,一道非常好的复习线段树的题目,包含了线段树所有基本操作
- HDU 4267 A Simple Problem with Integers
多颗线段树,将零散的偏移量由线段树变为连续的
8. 线段树维护信息举例
- POJ 3255 Help with Intervals
线段树与集合操作
- Codeforces #321 E. Kefa and Watch
线段树维护字符串哈希值