文章目录
前言(与树状数组比较)
树状数组,一般用来处理前缀和问题,但我们后来介绍了如何用树状数组来求区间最值,我个人感觉树状数组跟基本线段树的功能已经很接近了,甚至在空间复杂度上更具优势;但(1)树状数组能解决的问题用线段树都能解决,而且思路更加的直接,(2)线段树还能跟一些高端的知识联系在一起,所以学好线段树还是很有必要的。
一、原理与构造
1.原理
通过将区间不断二分成一个个小区间,我们可以快速确认一个区间进行操作,而且时间复杂度将维持在O(log n)。
2.4N空间的证明
4N空间证明
(我看不懂,随便看看吧,记得就好)
3.构造
建议用一个结构体把信息集合在一起,比如val跟lazy,甚至可以把区间左右端点位置和区间长度都放进去。
const int MAXN = 200005;
int a[MAXN];
struct node{
int val;
int lazy;
};
node tree[MAXN*4];
void pushup(int rt){//上推更新
tree[rt].val=tree[rt*2].val+tree[rt*2+1].val;
}
void build(int l,int r,int rt){
if(l==r){
tree[rt].val=a[l];
return ;
}
int mid=(l+r)/2;
build(l,mid,rt*2);
build(mid+1,r,rt*2+1);
pushup(rt);//在子树情况确定后要将信息上传
}
二、单点更新(初始版)
1.通过二分查找确定位置,再进行修改;
2.更新区间值。
void update(int L,int C,int l,int r,int rt){//单点更新
if(l==r){
if(a[l]<C){
a[l]=C;
tree[rt].val=C;
}
return ;
}
int mid = (l+r)/2;
if(L<=mid)update(L,C,l,mid,rt*2);
else update(L,C,mid+1,r,rt*2+1);
pushup(rt);
}
三、区间查询(初始版)
不考虑lazy的影响,直接对一个区间进行查询。
int query(int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
return tree[rt].val;
}
if(l>R||r<L)return 0;
int mid = (l+r)/2;
return query(L,R,l,mid,rt*2)+query(L,R,mid+1,r,rt*2+1);
}
例题:小牛比身高(范围极差问题)
例题:Cows(真子集问题)
四、区间更新(绝对修改,相对修改)
1.更新的类型
绝对修改:将一个值直接修改成另一个值,两个值之间一般不存在关系;
相对修改:对一个值进行增减操作;
还有的修改是对数据进行平方或者开方等操作,是通过添加不同的lazy标志来实现,这里不会讨论。
2.lazy标志的作用
在操作中,如果存在多次的更新操作,那么如果我们每次操作落实到每个点,相当于多次调用单点更新的操作,那么整体的时间复杂度会有点可怕。
那么我们不禁想到能否将多次的更新尽量进行简化,于是我们引入了lazy标志。
以那道兵营问题为例,我们要进行的操作有:
1.访问几个连续的兵营中士兵的总数;
2.对几个连续的兵营进行统一调配,对人数进行统一的增减。
如果存在多次区间修改,我们不妨先将更新的结果存在lazy中,直到需要进行访问的时候再进行下推,把lazy中存的值落实到每个数上。(在实际的操作中我们在更新时同样会进行下推操作,避免不同更新所产生的lazy之间的相互影响)
3.pushdown的作用
我们一般选择在往下递归之前进行lazy标志的下推,因为可能存在多次更新,不同的更新会产生不同的lazy,如果将所有的lazy都堆积在一起,可能会对最终的结果产生影响。
如果说我们只有一个
l
a
z
y
lazy
lazy标志,表示对区间内的每个数加上
l
a
z
y
lazy
lazy值,此时更新下推对结果无影响,在查询中下推即可;
但若我们有两种
l
a
z
y
lazy
lazy标志,一种表示
a
d
d
add
add,即加上某个值,另一种表示
s
e
t
set
set,表示区间数置为某个值。此时的更新下推就对最后的结果有了影响。
如果在更新时不进行下推,那么就需要在pushup操作中考虑lazy的影响,可能写法会比较麻烦,属于非常规写法,可以尝试但不赞同。
4.区间更新
void pushdown(int rt, int l, int r){
if (t[rt].lazy){
int mid=(l+r)/2;
t[rt*2].val+=t[rt].lazy*(mid - l + 1);
t[rt*2+1].val+=t[rt].lazy*(r-mid);
t[rt*2].lazy+=t[rt].lazy;
t[rt*2+1].lazy+=t[rt].lazy;
t[rt].lazy=0;
}
}
void update(int rt, int l, int r, int x, int y, int c){
if (x <= l && r <= y){
t[rt].val+=(r-l+1)*c; //仅修改该结点
t[rt].lazy+=c; //增加标记,子结点待修改
return;
}
pushdown(rt,l,r); //下传lazy标记
int mid=(l+r)/2;
if (x<=mid)update(rt*2,l,mid,x,y,c);
if (y>mid)update(rt*2+1,mid+1,r,x,y,c);
pushup(rt);
}
5.区间访问(改进版)
int query(int L,int R,int l,int r,int rt){//L,R为目标区间
if(L<=l&&r<=R){
return t[rt].val;
}
int ans=0;
pushdown(rt,l,r);
int mid= (l+r)/2;
if(L<=mid) ans+=query(L,R,l,mid,rt*2);
if(R>mid) ans+=query(L,R,mid+1,r,rt*2+1);
return ans;
}
五.标准模板
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
long long A[MAXN];
struct tnode
{
long long sum[2], lazy[2];
int l, r;
};
tnode operator + (const tnode &A, const tnode &B)
{
tnode C;
C.l = A.l;
C.r = B.r;
C.lazy[0] = 1;
C.lazy[1] = 0;
C.sum[0] = A.sum[0] + B.sum[0];
C.sum[1] = A.sum[1] + B.sum[1];
return C;
}
struct Segment_Tree
{
tnode t[4 * MAXN];
void init_lazy(int root)
{
t[root].lazy[0] = 1;
t[root].lazy[1] = 0;
}
void union_lazy(int fa, int ch)
{
long long temp[2];
temp[0] = t[fa].lazy[0] * t[ch].lazy[0];
temp[1] = t[fa].lazy[0] * t[ch].lazy[1] + t[fa].lazy[1];
t[ch].lazy[0] = temp[0];
t[ch].lazy[1] = temp[1];
}
void cal_lazy(int root)
{
t[root].sum[1] = t[root].lazy[0] * t[root].lazy[0] * t[root].sum[1] +
t[root].lazy[1] * t[root].lazy[1] * (t[root].r - t[root].l + 1) +
t[root].lazy[0] * t[root].lazy[1] * 2 * t[root].sum[0];
t[root].sum[0] = t[root].lazy[0] * t[root].sum[0] +
t[root].lazy[1] * (t[root].r - t[root].l + 1);
return;
}
void push_down(int root)
{
if (t[root].lazy[0] != 1 || t[root].lazy[1] != 0)
{
cal_lazy(root);
if (t[root].l != t[root].r)
{
int ch = root << 1;
union_lazy(root, ch);
union_lazy(root, ch + 1);
}
init_lazy(root);
}
}
void update (int root)
{
int ch = root << 1;
push_down(ch);
push_down(ch + 1);
t[root].sum[0] = t[ch].sum[0] + t[ch + 1].sum[0];
t[root].sum[1] = t[ch].sum[1] + t[ch + 1].sum[1];
}
void build(int root, int l, int r)
{
t[root].l = l;
t[root].r = r;
init_lazy(root);
if (l != r)
{
int mid = (l + r) >> 1;
int ch = root << 1;
build(ch, l, mid);
build(ch + 1, mid + 1, r);
update(root);
}
else
{
t[root].sum[0] = A[l];
t[root].sum[1] = A[l] * A[l];
}
}
void change(int root, int l, int r, long long delta, int op)
{
push_down(root);
if (l == t[root].l && r == t[root].r)
{
t[root].lazy[op] = delta;
return;
}
int mid = (t[root].l + t[root].r) >> 1;
int ch = root << 1;
if (r <= mid)change(ch, l, r, delta,op);
else if (l > mid)change(ch + 1, l, r, delta,op);
else {change(ch, l, mid, delta,op); change(ch + 1, mid + 1, r, delta,op);}
update(root);
}
tnode sum(int root, int l, int r)
{
push_down(root);
if (t[root].l == l && t[root].r == r)
{
return t[root];
}
int mid = (t[root].l + t[root].r) >> 1;
int ch = root << 1;
if (r <= mid)return sum(ch, l, r);
else if (l > mid)return sum(ch + 1, l, r);
else return sum(ch, l, mid) + sum(ch + 1, mid + 1, r);
}
};
Segment_Tree ST;
int n, m, op, l, r;
long long x;
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i)
{
scanf("%lld", &A[i]);
}
ST.build(1, 1, n);
for (int _ = 1; _ <= m; ++_)
{
scanf("%d %d %d", &op, &l, &r);
if (op >= 3)
{
scanf("%lld", &x);
ST.change(1, l, r, x, op - 3);
}
else
{
printf("%lld\n", ST.sum(1, l, r ).sum[op-1]);
}
}
return 0;
}