线段树
线段树是一种普通的高等树状数据结构。
基础的线段树可以解决
,查询
和
。
线段树是一棵二叉树,每个节点一定有两个或无孩子。
线段树传递参数太多,导致耗时加大,运行时间大于预估时间复杂度。
建树复杂度:
。
每个节点可以保存区间信息,那么左孩子可以保存前半个区间的信息,右孩子保存后半个区间的信息。两个孩子所记录的信息无交集 且 并集 等于 父亲所保存的信息。(信息即 元素)。当该节点只储存单个元素信息时,就不必再创造左右节点。
根据二叉树的特点,对每个节点编号。1号为祖先,储存整个区间的信息。
那么对于每个孩子,结合二叉树的特点,左孩子的编号是父亲的编号
,右孩子的编号是父亲的编号
。用这样的编号方式,编号数字不重不漏,最大化利用空间。递归建树法。
build_tree(1, 1, n);
void build_tree(int cur, int l, int r)
{
if(l == r)
{
tree[cur] = read();
return;
}
int mid = l + r >> 1;
build_tree(cur<<1, l, mid);
build_tree(cur<<1|1, mid+1, r);
tree[cur] = tree[cur<<1] + tree[cur<<1|1];
return;
}
查询复杂度:
。
查询 区间极值 或者 计算 区间和。是用分块的形式。
函数中查询
,分别表示:
点的编号,当前区间左区间,当前区间右区间,查询的左区间,查询的右区间。(从
号点开始查询,所以当前区间是
)
查询有三种情况:
.假设总区间是
,如果查询
,那么显然该查询当前区间的前一半,那么在递归调用的时候,下一步查询的就是
.假设总区间是
,如果查询
,那么显然该查询当前区间的后一半,那么在递归调用的时候,下一步查询的就是
.假设总区间是
,如果查询
,那么显然前两种方法都无法做到正确查询。这时候需要把当前的区间分裂成两个长度相等部分,查询的区间以当前区间的
,分成两部分。前半部分对应,后半部分也对应。
所以总区间的前部分 分成
,查询变成了
。后部分分成
,查询变成了
。然后分别递归,直到满足可以用查询的
才停止递归。
query_sum(1, 1, n, l, r);
void query_sum(int cur, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
ans += tree[cur];
return;
}
if(lazy[cur]) pushdown(cur, l, r);
int mid = l + r >> 1;
if(R <= mid) query_sum(cur<<1, l, mid, L, R);
else if(L > mid) query_sum(cur<<1|1, mid+1, r, L, R);
else query_sum(cur<<1, l, mid, L, mid), query_sum(cur<<1|1, mid+1, r, mid+1, R);
tree[cur] = tree[cur<<1] + tree[cur<<1|1];
}
区间修改复杂度:
区间加减也是用的分块的形式。
给一个区间每个数加
, 需要具体落实到每一个元素吗?不需要。(需要的话干嘛还要用线段树)
同“查询区间和”的代码长得几乎一样。如果当前区间 是 要加的区间 的子集,那么当前的和 直接
。就修改好当前点保存的区间的和的信息了。但是如果要查询该子集的子集,那么该子集的子集并没有更新啊,因为子集更新好了新的元素和,子集的子集并没有与更新元素和。所以用一个数组
。如果某个子集执行了更新和的操作,那么给这个点打一个lazy标记。如果以后要查询该点的子集,就在查询前进行
。 把当前点的标记更新给要用的区间,实现对子集的子集的更新。复杂度约等于
。
顺便附上
代码
update(1, 1, n, l, r, w);
void update(int cur, int l, int r, int L, int R, int w)
{
if(L <= l && r <= R)
{
tree[cur] += (r-l+1) * w;
lazy[cur] += w;
return;
}
if(lazy[cur]) pushdown(cur, l, r);
int mid = l + r >> 1;
if(R <= mid) update(cur<<1, l, mid, L, R, w);
else if(L > mid) update(cur<<1|1, mid+1, r, L, R, w);
else update(cur<<1, l, mid, L, mid, w), update(cur<<1|1, mid+1, r, mid+1, R, w);
tree[cur] = tree[cur<<1] + tree[cur<<1|1];
}
void pushdown(int cur, int l, int r)
{
int mid = l + r >> 1;
tree[cur<<1] += (mid-l+1) * lazy[cur];
tree[cur<<1|1] += (r-mid) * lazy[cur];
lazy[cur<<1] += lazy[cur];
lazy[cur<<1|1] += lazy[cur];
lazy[cur] = 0;
}
线段树完整代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
using namespace std;
int n,m,ans;
int tree[400400],lazy[400400];
int read()
{
int rt = 0, in = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') in = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {rt = rt * 10 + ch - '0'; ch = getchar();}
return rt * in;
}
void print(int x)
{
if(x < 0) putchar('-'), x = -x;
if(x > 9) print(x / 10);
putchar(x % 10 + '0');
}
void build_tree(int cur, int l, int r)
{
if(l == r)
{
tree[cur] = read();
return;
}
int mid = l + r >> 1;
build_tree(cur<<1, l, mid);
build_tree(cur<<1|1, mid+1, r);
tree[cur] = tree[cur<<1] + tree[cur<<1|1];
return;
}
void pushdown(int cur, int l, int r)
{
int mid = l + r >> 1;
tree[cur<<1] += (mid-l+1) * lazy[cur];
tree[cur<<1|1] += (r-mid) * lazy[cur];
lazy[cur<<1] += lazy[cur];
lazy[cur<<1|1] += lazy[cur];
lazy[cur] = 0;
}
void update(int cur, int l, int r, int L, int R, int w)
{
if(L <= l && r <= R)
{
tree[cur] += (r-l+1) * w;
lazy[cur] += w;
return;
}
if(lazy[cur]) pushdown(cur, l, r);
int mid = l + r >> 1;
if(R <= mid) update(cur<<1, l, mid, L, R, w);
else if(L > mid) update(cur<<1|1, mid+1, r, L, R, w);
else update(cur<<1, l, mid, L, mid, w), update(cur<<1|1, mid+1, r, mid+1, R, w);
tree[cur] = tree[cur<<1] + tree[cur<<1|1];
}
void query_sum(int cur, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
ans += tree[cur];
return;
}
if(lazy[cur]) pushdown(cur, l, r);
int mid = l + r >> 1;
if(R <= mid) query_sum(cur<<1, l, mid, L, R);
else if(L > mid) query_sum(cur<<1|1, mid+1, r, L, R);
else query_sum(cur<<1, l, mid, L, mid), query_sum(cur<<1|1, mid+1, r, mid+1, R);
tree[cur] = tree[cur<<1] + tree[cur<<1|1];
}
int main()
{
n = read(), m = read();
build_tree(1,1,n);
for(int i = 1; i <= m; i++)
{
int ins = read();
if(ins == 1)
{
int l = read(), r = read(), w = read();
update(1, 1, n, l, r, w);
}
if(ins == 2)
{
int l = read(), r = read();
ans = 0;
query_sum(1, 1, n, l, r);
print(ans),putchar('\n');
}
}
return 0;
}
例题:
[模版]线段树
luogu P1637
luogu P4145
多写写线段树,就会用还不会写错了