线段树是一种基于分治思想的二叉树结构,用于在区间上进行信息统计。
相对于树状数组,线段树更加通用,代码更长,功能也更加强大。
线段树可以在 O(logn) 的时间复杂度内实现单点修改、单点查询、区间修改、区间查询(区间求和,区间求最大值,区间求最小值、区间求异或 etc)等操作。
线段树的一些性质:
1、每个节点都代表一个区间
2、根节点代表的区间是整个统计范围,如 [1, N]
3、线段树的每个叶节点都代表长度为 1 的元区间 [x, x]
4、对于每个内部节点 [l, r],它的左子节点是 [l, mid],右子节点是 [mid+1, r],其中 mid = (l+r) / 2
5、因为是二叉树,所以树的深度为 logn
6、通常我们把根节点编号为1,编号为 x 的节点的左子节点为 x * 2,右子节点为 x * 2 + 1
7、当统计范围为 N 时,线段树数组要不小于 4 * N
线段树的基本操作:
1、push_up,更新和值
2、build,建树操作
3、update,区间修改
4、spread(也可以叫push_down),下传懒标记
5、query,区间查询
线段树的基本结构:
ll a[N];
struct Node {
int l, r;
ll sum, maxv, lazy;
void updade(ll x) {
sum += (r - l + 1) * x;
maxv += x;
lazy += x;
}
} tree[N * 4];
更新和值:
更新 p 的两个子节点后,更新 p 的值
void push_up(int p)
{
tree[p].sum = tree[p << 1].sum + tree[p << 1 | 1].sum;
tree[p].maxv = max(tree[p << 1].maxv, tree[p << 1 | 1].maxv);
}
建树操作:
以 p 为根节点,l 为左端点,r 为右端点,建立一棵二叉树
void build(int p, int l, int r)
{
tree[p].l = l, tree[p].r = r;
tree[p].sum = tree[p].lazy = 0;
if (l == r) {
tree[p].sum = tree[p].maxv = a[l];
return;
}
int mid = l + r >> 1;
build(p << 1, l , mid);
build(p << 1 | 1, mid + 1, r);
push_up(p);
}
下传懒标记:
将节点 p 的懒标记传递下去
void spread(int p)
{
int lazy_val = tree[p].lazy;
if (lazy_val) {
tree[p << 1].updade(lazy_val);
tree[p << 1 | 1].updade(lazy_val);
tree[p].lazy = 0;
}
}
区间修改:
将区间 [l, r] 都加上 val
p 为当前的根节点,l、r 为当前区间的左右端点,val 为增加的值
void update(int p, int l, int r, ll val)
{
if (l <= tree[p].l && tree[p].r <= r) {
tree[p].updade(val);
} else {
spread(p);
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid)
update(p << 1, l, r, val);
if (r > mid)
update(p << 1 | 1, l, r, val);
push_up(p);
}
}
区间查询操作:
查询区间 [l, r] 的和
ll query(int p, int l, int r)
{
if (l <= tree[p].l && tree[p].r <= r)
return tree[p].sum;
spread(p);
ll ans = 0;
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid)
ans = ans + query(p << 1, l, r);
if (r > mid)
ans = ans + query(p << 1 | 1, l, r);
return ans;
}
查询区间 [l, r] 的最大值
ll query(int p, int l, int r)
{
if (l <= tree[p].l && tree[p].r <= r)
return tree[p].maxv;
spread(p);
ll ans = -(1 << 30);
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid)
ans = max(ans, query(p << 1, l, r));
if (r > mid)
ans = max(ans, query(p << 1 | 1, l, r));
return ans;
}
学姐的代码模板:
区间求和
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;
typedef long long ll;
ll a[N];
int n;
struct Node {
int l, r;
ll sum, maxv, lazy;
void updade(ll x) {
sum += (r - l + 1) * x;
maxv += x;
lazy += x;
}
} tree[N * 4];
void push_up(int p)
{
tree[p].sum = tree[p << 1].sum + tree[p << 1 | 1].sum;
tree[p].maxv = max(tree[p << 1].maxv, tree[p << 1 | 1].maxv);
}
void build(int p, int l, int r)
{
tree[p].l = l, tree[p].r = r;
tree[p].sum = tree[p].lazy = 0;
if (l == r) {
tree[p].sum = tree[p].maxv = a[l];
return;
}
int mid = l + r >> 1;
build(p << 1, l , mid);
build(p << 1 | 1, mid + 1, r);
push_up(p);
}
void spread(int p)
{
int lazy_val = tree[p].lazy;
if (lazy_val) {
tree[p << 1].updade(lazy_val);
tree[p << 1 | 1].updade(lazy_val);
tree[p].lazy = 0;
}
}
void update(int p, int l, int r, ll val)
{
if (l <= tree[p].l && tree[p].r <= r) {
tree[p].updade(val);
} else {
spread(p);
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid)
update(p << 1, l, r, val);
if (r > mid)
update(p << 1 | 1, l, r, val);
push_up(p);
}
}
ll query(int p, int l, int r)
{
if (l <= tree[p].l && tree[p].r <= r)
return tree[p].sum;
spread(p);
ll ans = 0;
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid)
ans = ans + query(p << 1, l, r);
if (r > mid)
ans = ans + query(p << 1 | 1, l, r);
return ans;
}
int main()
{
int q, l, r;
int opt;
ll val;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%lld", a + i);
build(1, 1, n);
scanf("%d", &q);
while (q--) {
scanf("%d", &opt);
if (opt == 2) {
scanf("%d %d", &l, &r);
printf("%lld\n", query(1, l, r));
} else {
scanf("%d %d %lld", &l, &r, &val);
update(1, l, r, val);
}
}
return 0;
}
区间求最大值
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
const int N = 1e5 + 10;
typedef long long ll;
ll a[N];
int n;
struct Node {
int l, r;
ll sum, maxv, lazy;
void updade(ll x) {
sum += (r - l + 1) * x;
maxv += x;
lazy += x;
}
} tree[N * 4];
void push_up(int p)
{
tree[p].sum = tree[p << 1].sum + tree[p << 1 | 1].sum;
tree[p].maxv = max(tree[p << 1].maxv, tree[p << 1 | 1].maxv);
}
void build(int p, int l, int r)
{
tree[p].l = l, tree[p].r = r;
tree[p].sum = tree[p].lazy = 0;
if (l == r) {
tree[p].sum = tree[p].maxv = a[l];
return;
}
int mid = l + r >> 1;
build(p << 1, l , mid);
build(p << 1 | 1, mid + 1, r);
push_up(p);
}
void spread(int p)
{
int lazy_val = tree[p].lazy;
if (lazy_val) {
tree[p << 1].updade(lazy_val);
tree[p << 1 | 1].updade(lazy_val);
tree[p].lazy = 0;
}
}
void update(int p, int l, int r, ll val)
{
if (l <= tree[p].l && tree[p].r <= r) {
tree[p].updade(val);
} else {
spread(p);
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid)
update(p << 1, l, r, val);
if (r > mid)
update(p << 1 | 1, l, r, val);
push_up(p);
}
}
ll query(int p, int l, int r)
{
if (l <= tree[p].l && tree[p].r <= r)
return tree[p].maxv;
spread(p);
ll ans = -(1 << 30);
int mid = tree[p].l + tree[p].r >> 1;
if (l <= mid)
ans = max(ans, query(p << 1, l, r));
if (r > mid)
ans = max(ans, query(p << 1 | 1, l, r));
return ans;
}
int main()
{
int q, l, r;
int opt;
ll val;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%lld", a + i);
build(1, 1, n);
scanf("%d", &q);
while (q--) {
scanf("%d", &opt);
if (opt == 2) {
scanf("%d %d", &l, &r);
printf("%lld\n", query(1, l, r));
} else {
scanf("%d %d %lld", &l, &r, &val);
update(1, l, r, val);
}
}
return 0;
}