线段树算法总结(C/C++)

一: 线段树算法分析


1. 基本分析

a.线段树——处理区间查询 / 维护 / 状态更新问题

 例如下图:
在这里插入图片描述
 假如我们提出多次的区间修改和查询要求,如果修改是找到指定位置的小方块例如( 4-6方块 + 2 操作),查询也是查询一个区间的和这个样子的操作,如果我们直接对指定区间去逐个修改查询的话这样子的时间复杂度毫无疑问是非常大的。

 例如下图:
在这里插入图片描述
所以:我们选择一个高效的算法—线段树,去动态储存一段区间的信息,当我们需要再次查找修改信息的时候我们就可以不需要每次再对底层的单个区间进行修改。
 这样子当我们再去需要查询 4 - 6 方块的信息的时候起始我们只要按顺序从上到下处理,查询 4 - 5的时候并不需要走到底层查询, 而可以直接查询4 - 5方块信息就可以了。

2. lazy标记

2.1 lazy标记——帮助优化区间的修改

现在重要的问题就在于我们应该什么时候可以去不走到底层修改,什么时候需要走到底层修改, 例如区间 4 - 6 我们分开走走到 4 - 5是不需要再向下走了, 而走到 6 是必须要继续向下走的去修改单独的 6 模块。
并且关键的是我们修改了 4 - 5 区间模块,是没有修改底层 4 和 5 模块的,假如下次我们需要修改 4 模块,我们也是必须走到底层 4 模块 并且需要把上一次对 4 模块的修改一起带到 4 模块去。
   例如 :
   第一次: 4 - 5 模块 + 2 ,实际 4 模块 = 0.
   第二次: 4 模块 + 3 ,实际4模块修改的结果应该是 之前需要修改的+2 和这次需要修改的 +3,所以 实际 4模块 = 5.

2.2 lazy标记——分析和实现

分析过程:
 a. 利用lazy[ ]标记我们可以走到这个区间后本来需要向下修改但被我们省略了向下修改的步骤
 b. 只有当下次需要继续向下修改的时候我们再一起把lazy[ ]标记下放

实现过程:
 a. 什么时候可以省略向下继续修改?
 ——当我们需要修改的区间[l, r] 可以包括住当前所代表的区区间[st ,ed] 的时候,例如 [l = 4, r = 6 ], [st = 4, ed = 5] 此时我们就可以不需要再继续向下修改了,只要修改这个 4 - 5模块,并将 lazy[ ]标记相应修改,只有当再次需要下放的时候再下放。

if(st >= l && ed <= r ){ // l 和 r 可以完全包裹住这个区间
	lazy[node] = solve();
	tree[node] = solve();
}

 b. 什么时候要将lazy[ ]标记下放
 ——和省略向下修改步骤原理相反,当你不能再完全将需要修改的模块完全包住的时候就需要下放了,例如你想修改[l = 5, r = 7], [st = 4, ed = 5]这时候你需要修改5但不要修改4,你需要继续向下修改就要先把之前所欠的没有修改的lazy标记进行下放操作

/*
rt : 区间标号 st , ed 此区间包括范围
l, r : 需要修改的区间
k : 需要修改的值
*/
void update(LL rt, LL st, LL ed, LL l, LL r, LL k){ 
	/*
	不能完全包裹时 : push_down() 需要下放标记
	下放lazy[]标记 ———— 更新下面区间 ————利用已更新好小区间更新这个区间
	*/
    push_down(rt, st, ed);
    LL mid = (st + ed) >> 1;
    if(l <= mid) update1(lrt, st, mid, l, r, k);
    if(r > mid ) update1(rrt, mid + 1, ed, l, r, k);
    push_up(rt, st, ed);
}

3. 动态灵活增加更多标记

a. 为什么需要其他标记?
 ——很简单因为题目需要,当我们不再是对一个模块单纯求和而需要求其最大值,最小值,等等其他限定条件的时候,我们可以同时在一个模块当中塞入其他标记帮助我们更好解题

例如下图:
在这里插入图片描述

二: 线段树基本代码

// rt, st, ed : 区间号码 + 区间左边界 + 区间右边界
// l, r : 需要查询的左右区间
// lrt = rt >> 1, rrt = rt >> 1 | 1

1. 标记下放 + 向上修改区间信息代码

下放标记 —— 需要清空lazy[ ]标记

void push_down(int rt, int st, int ed ){
    if(lazy[rt]){
        int mid = (st + ed) >> 1;
        tree[lrt] += (mid - st +1) * lazy[rt];
        lazy[lrt] += lazy[rt];

        tree[rrt] += (ed - mid) * lazy[rt];
        lazy[rrt] += lazy[rt];
        
        lazy[rt] = 0;
    }
}

向上整合区间信息

void push_up(int rt, int st, int ed ){
    tree[rt] = tree[lrt] + tree[rrt];
}

2. 建树代码

先建立子树 —— 利用子树信息更新父树

//数组地址,建树的地址,树的起始结点,数的起始和结束位置.
void build_tree(int arr[], int tree[], int rt, int st, int ed){
	if(sta == end){
		tree[rt] = arr[sta];
		return;
	}
	int mid = (st + ed) >> 1;
	build_tree(arr, tree, lrt, st, mid);
	build_tree(arr, tree, rrt, mid+1, ed);
	push_up(rt, st, ed);
}

3. 区间修改代码

区间修改2个步骤
 a. 可以直接修改大区间 —— 修改区间
 b. 需要继续向下修改 —— 下放标记 + 修改子区间 + 信息整合

void update(int rt, int st,int ed, int l, int r, int k){ // k是需要操作的大小
    if(st >= l && ed <= r){
        tree[rt] += (ed - st +1) * k;
        lazy[rt] += k;
        mx[rt] += k ,mn[rt] += k;
        return ;
    }

    push_down(rt, st, ed);
    LL mid = (st + ed) >> 1;
    if(l <= mid) update(lrt, st, mid, l, r, k);
    if(r > mid ) update(rrt, mid + 1, ed, l, r, k);
    push_up(rt, st, ed);
}

4. 区间查询代码

区间查询2个步骤
 a. 可以直接查询大区间 —— 返回区间结果
 b. 需要继续向下查询 —— 下放标记 + 查询子区间 + 信息整合

int query(int rt, int st, int ed, int l, int r){
    if(st >= l && ed <= r) return tree[rt];

    push_down(rt, st, ed);
    LL mid = (st + ed) >> 1;
    LL sum = 0;
    if(l <= mid)sum += query(lrt, st, mid, l, r);
    if(r > mid)sum += query(rrt, mid+1, ed, l, r);
    return sum;
}

三: 例题 分析+细节+代码


a. 例题
2022年江西省大学生程序设计竞赛K题

在这里插入图片描述

b. 分析
 此题涉及一些限制条件,数最小不能小于0,所以我们可以设置一些额外标记mn[ ], mx[ ]等,正常数值加直接update,数值如果为负则需要利用mx[ ],mn[ ]等进行判断能否对这个区间进行一个区间的操作等,并且设置额外cl[ ]标记看是否需要将下面区间进行清空等等。

c. 细节注意
 查询和修改操作:两个大步骤分类
在这里插入图片描述
 lazy标记,cl标记 :都是判断是否需要对子区间进行处理,本身这个区间已经处理好了
细节注意

d. 完整代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<map>
#define lrt rt << 1
#define rrt rt << 1 | 1
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL tree[N << 2], lazy[N << 2], mx[N << 2], mn[N << 2], n, q;
bool cl[N << 2];
 
void push_down(LL rt, LL st, LL ed ){
    if(cl[rt]){
        tree[lrt] = lazy[lrt] = mx[lrt] = mn[lrt] = 0;
        tree[rrt] = lazy[rrt] = mx[rrt] = mn[rrt] = 0;
        cl[lrt] = cl[rrt] = true ;
        cl[rt] = false;
    }
    if(lazy[rt]){
        LL mid = (st + ed) >> 1;
        tree[lrt] += (mid - st +1) * lazy[rt];
        lazy[lrt] += lazy[rt];
        mx[lrt] += lazy[rt], mn[lrt] += lazy[rt];
 
        tree[rrt] += (ed - mid) * lazy[rt];
        lazy[rrt] += lazy[rt];
        mx[rrt] += lazy[rt], mn[rrt] += lazy[rt];
        lazy[rt] = 0;
    }
}
 
void push_up(LL rt, LL st, LL ed ){
    tree[rt] = tree[lrt] + tree[rrt];
    mx[rt] = max(mx[lrt], mx[rrt] );
    mn[rt] = min(mn[lrt], mn[rrt] );
}
 
LL query(LL rt, LL st, LL ed, LL l, LL r){
    if(st >= l && ed <= r) return tree[rt];
 
    push_down(rt, st, ed);
    LL mid = (st + ed) >> 1;
    LL sum = 0;
    if(l <= mid)sum += query(lrt, st, mid, l, r);
    if(r > mid)sum += query(rrt, mid+1, ed, l, r);
    return sum;
}
 
void update1(LL rt, LL st, LL ed, LL l, LL r, LL k){ // 更新正值
    if(st >= l && ed <= r){
        tree[rt] += (ed - st +1) * k;
        lazy[rt] += k;
        mx[rt] += k ,mn[rt] += k;
        return ;
    }
 
    push_down(rt, st, ed);
    LL mid = (st + ed) >> 1;
    if(l <= mid) update1(lrt, st, mid, l, r, k);
    if(r > mid ) update1(rrt, mid + 1, ed, l, r, k);
    push_up(rt, st, ed);
}
 
 
 
void update2(LL rt, LL st, LL ed, LL l, LL r, LL k){
 
    if(st == ed){
        tree[rt] = max( (LL)0, tree[rt] - k );
        mn[rt] = mx[rt] = tree[rt];
        return ;
    }
 
    if(st >= l && ed <= r){
        if(k >= mx[rt]){
            tree[rt] = lazy[rt] = mn[rt] = mx[rt] = 0;
            cl[rt] = true;
            return ;
        }
        else if(k <= mn[rt]){
            tree[rt] -= (ed - st +1) * k;
            lazy[rt] -= k;
            mn[rt] -= k, mx[rt] -= k;
            return ;
        }
    }
 
    push_down(rt, st, ed);
    LL mid = (st + ed) >> 1;
    if(l <= mid) update2(lrt, st, mid, l, r, k);
    if(r > mid ) update2(rrt, mid + 1, ed, l, r, k);
    push_up(rt, st, ed);
 
 
}
int main(){
    ios::sync_with_stdio(false); cin.tie(0);
 
    cin >> n >> q;
    while(q -- ){
        LL m, a, b, res=0;
        cin >> m >> a >> b;
        if(m == 0){
            LL res = query(1, 1, n, a, b);
            cout << res << endl;
        }
        else if(m > 0){
            update1(1, 1, n, a, b, m);
        }
        else{
            update2(1, 1, n, a, b, -m);
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

psudd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值