当前位置: 首页 > news >正文

线段树入门

对于一个区间进行询问,进行修改,都是用线段树进行处理。

线段树和普通的树不一样,普通的树的节点存的是一个编号,线段树存的是一个区间,而且线段树一定是一棵完全二叉树。例如:

这就是一棵线段树。

例如对于[1...8](也就是1号根节点)这个节点来说,

[1...4](也就是2号节点)是它的lson(左儿子)

[5...8](也就是3号节点)是它的rson(右儿子)

并且和二叉树一样,(其中p为当前节点)

线段树可以给区间加上某个值 ,乘以某个值,覆盖某个值,取最大值 , 查询,单点查询,区间查询,查询和,查询最大最小值,查询区间最大字段和,查询各种奇怪的东西。(如果两个区间可以合并,所有的信息合并,那么就可以查询.

比如说区间最大字段和。

比如说我们找区间的最大字段和。

表示区间的最大字段和, 我们已知的所有信息,我们要得到的所有信息。

所以;

其中表示前缀和的最大值,表示后缀和的最大值。

设tot[p]表示这一段的总和。

遍历时就用类似二分的做法枚举区间就行了

(其实蛮简单的)

例题:单点修改,区间查询

一道模板题,详见代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
#define ls p+p//左儿子
#define rs p+p+1//右儿子
long long sum[maxn*4],ans;//记得开4倍
int a[maxn],n,q;
void update(int p){
    sum[p]=sum[ls]+sum[rs];//因为是加和,所以当前节点为它的左右儿子的和
}
void build(int p,int l,int r){//建树,分别表示当前节点编号,区间左端点和右端点
    if(l==r) {//如果到了叶子节点
        sum[p]=a[l];//初值
        return;
    }
    int mid=l+r>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    update(p);
}
//a[x]+=v;
void change(int p,int l,int r,int x,int v){//更改,参数同上
    if(l==r&&l==x){//如果是这个要加的节点
        sum[p]+=v;
        return;
    }
    int mid=l+r>>1;
    if(x<=mid)change(ls,l,mid,x,v);
    else change(rs,mid+1,r,x,v);
    update(p);//记得更改
}
void query(int p,int l,int r,int x,int y){
    if(x<=l&&r<=y) {//如果是这个区间
        ans+=sum[p];//加上答案
        return;
    }
    int mid=l+r>>1;
    if(x<=mid)query(ls,l,mid,x,y);
    if(y>mid)query(rs,mid+1,r,x,y);
}
int main(){
    cin>>n>>q;
    for(int i=1;i <=n;i++)cin>>a[i];
    build(1,1,n);
    while(q--){
        int a,b,c;
        cin>>a>>b>>c;
        if(a==1)change(1,1,n,b,c);
        else{
            ans=0;
            query(1,1,n,b,c);
            cout<<ans<<endl;
        }
    }
}

如果是要求区间最大/值的话,就修改update函数就行了。

那如何进行区间修改呢?

那需要打上lazy标记。

比如只有区间加法。 那么你应该修改change函数和query函数,

并增加一个push操作,用来下传标记。

如果还是不明白懒标记的话,

就去看这个吧

void change2(int p,int l,int r,int x,int y,int z) {
    if(x<=l&&r<=y){
        sum[p]+=(r-l+1)*z;//当前节点应+=区间长度*懒标记
        add[p]+=z;//加上懒标记
        return;
    }
    int mid=l+r>>1;
    if(x<=mid)change2(ls,l,mid,x,y,z);
    if(y>mid)change2(rs,mid+1,r,x,y,z);
    update(p);
}
void push(int p,int l,int r){//一般的区间加法的标记传递
    int mid=l+r>>1;
    sum[lson]+=(mid-l+1)*lazy[p];
    sum[rson]+=(r-mid)*lazy[p];
    lazy[ls]+=lazy[p];//记得把标记传递下去。
    lazy[rs]+=lazy[p];
    lazy[p]=0;//标记使用过后要清空。
}
void query(int p,int l,int r,int x){
    if(l==r){
        更新答案
    return;
    }
    push(p,l,r);//传递标记
    int mid=l+r>>1;
    if(x<=mid)query(lson,l,mid,x);
    else query(rson,mid+1,r,x);
} 

我相信你一定会觉得线段树灰常简单的,对吧?

相关文章:

  • 【C语言从0到1之文件操作】(原理 画图 举例 不信教不会你 不要放收藏夹落灰 学起来好嘛)
  • java的数据类型:引用数据类型(String、数组、枚举)
  • linux系统管理
  • 2023牛客寒假算法基础集训营2(11/12)
  • linux搭建webapp实战
  • Golang语法快速上手3
  • 【JavaEE初阶】第五节.多线程 ( 基础篇 ) 线程安全问题(上篇)
  • Java技能树-操作符(二)-练习篇
  • 化繁为简、性能提升 -- 在WPF程序中,使用Freetype库心得
  • 用通知-等待机制优化锁等待问题
  • 微电网(风、光、储能、需求响应)【Simulink 仿真实现】
  • Mysql安全之权限用户管理参考手册
  • C语言萌新如何使用printf函数?
  • 【Kotlin】类的继承 ① ( 使用 open 关键字开启类的继承 | 使用 open 关键字开启方法重写 )
  • [网鼎杯 2020 青龙组]AreUSerialz
  • Windows 服务器刷题(带答案)
  • docker入门(二):docker的常用命令
  • colab 如何释放gpu显存?
  • CANoe-仿真总线上的红蓝线、“CANoe DEMO“ license下的软件限制
  • SpringBoot 参数接收只看这一篇文章就够了
  • https://app.hackthebox.com/machines/Inject
  • Spring —— Spring简单的读取和存储对象 Ⅱ
  • 渗透测试之冰蝎实战
  • Mybatis、TKMybatis对比
  • Microsoft Office 2019(2022年10月批量许可版)图文教程
  • 《谷粒商城基础篇》分布式基础环境搭建
  • 哈希表题目:砖墙
  • Vue 3.0 选项 生命周期钩子
  • 【车载嵌入式开发】AutoSar架构入门介绍篇
  • 【计算机视觉 | 目标检测】DETR风格的目标检测框架解读