线段树入门

7 篇文章 0 订阅
6 篇文章 0 订阅


前言(与树状数组比较)

树状数组,一般用来处理前缀和问题,但我们后来介绍了如何用树状数组来求区间最值,我个人感觉树状数组跟基本线段树的功能已经很接近了,甚至在空间复杂度上更具优势;但(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;
}

六.例题

例题:颜色计数
例题:市长的海报


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值