数据结构与算法-数据结构-线段树1(单点修改,区间查询):最大数,你能回答这些问题吗

devtools/2025/4/1 12:18:46/

介绍:
概念

线段树是一种二叉树数据结构,它用于高效处理区间查询(如求区间和、区间最大值等)和单点修改、区间修改操作。线段树的每个节点代表一个区间,根节点代表整个待处理的区间,每个内部节点将其代表的区间分成两部分,分别由其左右子节点表示。

思想

线段树的核心思想是分治。它将一个大区间不断地分解成较小的区间,直到每个区间只包含一个元素。通过预先计算和存储这些小区间的信息,我们可以在查询时快速地合并这些信息,从而高效地处理大区间的查询请求。同时,对于修改操作,我们只需要更新涉及到的节点信息,而不需要遍历整个区间。

功能

线段树主要用于解决以下两类问题:

单点修改,区间查询:

在某个位置修改元素的值,并查询某个区间内元素的信息(如和、最大值、最小值等)。

typedef struct node {

    int l, r, M;

} node;

node tr[N * 4];

int pre = 0;

// 向上更新父节点信息

void pushup(int u) {

    tr[u].M = max(tr[u << 1].M, tr[u << 1 | 1].M);

}

// 构建线段树

void build(int u, int l, int r) {

    if (l == r) {

        tr[u] = {l, r, 0};

    } else {

        int mid = (l + r) >> 1;

        tr[u] = {l, r, 0};

        build(u << 1, l, mid);

        build(u << 1 | 1, mid + 1, r);

        pushup(u);

    }

}

// 区间查询

int query(int u, int l, int r) {

    if (l <= tr[u].l && tr[u].r <= r) {

        return tr[u].M;

    } else {

        int mid = (tr[u].l + tr[u].r) >> 1;

        int res = 0;

        if (mid >= l) {

            res = query(u << 1, l, r);

        }

        if (mid < r) {

            res = max(res, query(u << 1 | 1, l, r));

        }

        return res;

    }

    return 0;

}

// 单点修改

void modify(int u, int x, int v) {

    if (x == tr[u].l && tr[u].r == x) {

        tr[u].M = v;

    } else {

        int mid = (tr[u].l + tr[u].r) >> 1;

        if (mid >= x) modify(u << 1, x, v);

        else modify(u << 1 | 1, x, v);

        pushup(u);

    }

}

不光可以求这个max,还可以求sum等等

pushup 函数:用于更新父节点的信息,这里是取左右子节点的最大值。

build 函数:递归构建线段树,将整个区间不断地二分,直到每个区间只包含一个元素。

query 函数:递归查询指定区间的信息,如果当前节点的区间完全包含在查询区间内,则直接返回该节点的信息;否则,分别查询左右子节点,并合并结果。

modify 函数:递归修改指定位置的元素值,并更新涉及到的父节点信息。

区间修改,区间查询:

对某个区间内的所有元素进行统一修改,并查询某个区间内元素的信息。

对于区间修改,我们需要引入懒标记(Lazy Tag)来优化时间复杂度。懒标记的作用是在区间修改时,不立即更新所有受影响的节点,而是先将修改信息记录在父节点上,等到需要查询这些节点时再将标记下放到子节点。

typedef struct node {

    int l, r, M;

    int lazy; // 懒标记

} node;

node tr[N * 4];

int pre = 0;

// 向上更新父节点信息

void pushup(int u) {

    tr[u].M = max(tr[u << 1].M, tr[u << 1 | 1].M);

}

// 懒标记下放

void pushdown(int u) {

    if (tr[u].lazy) {

        tr[u << 1].M += tr[u].lazy;

        tr[u << 1].lazy += tr[u].lazy;

        tr[u << 1 | 1].M += tr[u].lazy;

        tr[u << 1 | 1].lazy += tr[u].lazy;

        tr[u].lazy = 0;

    }

}

// 构建线段树

void build(int u, int l, int r) {

    tr[u] = {l, r, 0, 0};

    if (l == r) {

        return;

    }

    int mid = (l + r) >> 1;

    build(u << 1, l, mid);

    build(u << 1 | 1, mid + 1, r);

    pushup(u);

}

// 区间查询

int query(int u, int l, int r) {

    if (l <= tr[u].l && tr[u].r <= r) {

        return tr[u].M;

    }

    pushdown(u);

    int mid = (tr[u].l + tr[u].r) >> 1;

    int res = 0;

    if (mid >= l) {

        res = query(u << 1, l, r);

    }

    if (mid < r) {

        res = max(res, query(u << 1 | 1, l, r));

    }

    return res;

}

// 区间修改

void modify(int u, int l, int r, int v) {

    if (l <= tr[u].l && tr[u].r <= r) {

        tr[u].M += v;

        tr[u].lazy += v;

        return;

    }

    pushdown(u);

    int mid = (tr[u].l + tr[u].r) >> 1;

    if (mid >= l) {

        modify(u << 1, l, r, v);

    }

    if (mid < r) {

        modify(u << 1 | 1, l, r, v);

    }

    pushup(u);

}

这里的懒标记可以不止一个,可以通过后面的题看出来


最大数:

题目描述

现在请求你维护一个数列,要求提供以下两种操作:

查询操作。

语法:Q L

功能:查询当前数列中末尾 L 个数中的最大的数,并输出这个数的值。

限制:L 不超过当前数列的长度。(L>0)

插入操作。

语法:A n

功能:将 n 加上 t,其中 t 是最近一次查询操作的答案(如果还未执行过查询操作,则 t=0),并将所得结果对一个固定的常数 D 取模,将所得答案插入到数列的末尾。

限制:n 是整数(可能为负数)并且在长整范围内。

注意:初始时数列是空的,没有一个数。

输入格式

第一行两个整数,M 和 D,其中 M 表示操作的个数,D 如上文中所述。

接下来的 M 行,每行一个字符串,描述一个具体的操作。语法如上文所述。

输出格式

对于每一个查询操作,你应该按照顺序依次输出结果,每个结果占一行。

分析:

一个涉及区间查询和单点修改的题目,可以尝试用树状数组or线段树来解决,下面是线段树的解法:

代码:

#include<bits/stdc++.h>

using namespace std;

const int N=200010;

int n,p,m;

typedef struct node{

    int l,r,M;

}node;

node tr[N*4];

int pre=0;

void pushup(int u){

    tr[u].M=max(tr[u<<1].M,tr[u<<1|1].M);

}

void build(int u,int l,int r){

    if(l==r){

        tr[u]={l,r,0};

    }else {

        int mid=(l+r)>>1;

        tr[u]={l,r,0};

        build(u<<1,l,mid);

        build(u<<1 | 1,mid+1,r);

        pushup(u);

    }

}

int  query(int u,int l,int r){

    if(l<=tr[u].l&&tr[u].r<=r){

        return tr[u].M;

    }else {

        int mid =(tr[u].l+tr[u].r)>>1;

        int res=0;

        if(mid>=l){

            res=query(u<<1,l,r);

        }

        if(mid<r){

            res=max(res,query(u<<1|1,l,r));

        }

        return res;

    }

    return 0;

}

void modify(int u,int x,int v){

    if(x==tr[u].l&&tr[u].r==x){

        tr[u].M=v;

    }else{

        int mid =(tr[u].l+tr[u].r)>>1;

        if(mid>=x)modify(u<<1,x,v);

        else modify(u<<1|1,x,v);

        pushup(u);

    }

}

int  main(){

    scanf("%d%d",&m,&p);

    n=0;

    pre=0;

    build(1,1,m);

    while(m--){

        char op[2];

        int val;

        scanf("%s%d",op,&val);

        if(op[0]=='Q'){

            pre=query(1,n-val+1,n);

            cout<<pre<<endl;

        }else {

            int tmp=((val+pre)%p+p)%p;

            modify(1,n+1,tmp);

            n++;

        }

    }

}

你能回答这些问题吗

给定长度为 NN 的数列 AA,以及 MM 条指令,每条指令可能是以下两种之一:

1 x y,查询区间 [x,y][x,y] 中的最大连续子段和,即 maxx≤l≤r≤ymaxx≤l≤r≤y{∑i=lrA[i]∑i=lrA[i]}。

2 x y,把 A[x]A[x] 改成 yy。

对于每个查询指令,输出一个整数表示答案。

输入格式

第一行两个整数 N,MN,M。

第二行 NN 个整数 A[i]A[i]。

接下来 MM 行每行 33 个整数 k,x,yk,x,y,k=1k=1 表示查询(此时如果 x>yx>y,请交换 x,yx,y),k=2k=2 表示修改。

输出格式

对于每个查询指令输出一个整数表示答案。

每个答案占一行。

分析:


对于一个node来说,在l~r区间的一个最大的连续字段和可以是:在左儿子l中最大的连续字段和,在右儿子r中的最大的连续字段和,横跨左儿子和右儿子的连续字段和,针对这个设计出的node

node{

 int l,r;

 int maxc,lc,rc;

}

但是我们现在就要多的去维护lc和rc,考虑lc是这么得到的:左儿子的lc,左儿子的sum和右儿子的lc,这样就可以求出来lc了,那么rc同理

sum很好维护,这个线段树的模版

代码:

#include<bits/stdc++.h>

using namespace std;

int inf=-0x3f3f3f3f;

const int N=500010;

int n,m;

struct node{

    int l,r,maxc,lc,rc,sum;

}tr[N*4];

void pushup(int u){

    tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;

    tr[u].lc=max(tr[u<<1].lc,tr[u<<1].sum+tr[u<<1|1].lc);

    tr[u].rc=max(tr[u<<1|1].rc,tr[u<<1|1].sum+tr[u<<1].rc);

    tr[u].maxc=max(tr[u<<1].maxc,max(tr[u<<1|1].maxc,tr[u<<1].rc+tr[u<<1|1].lc));

}

void build(int u,int l,int r){

    if(l==r){

        tr[u]={l,r,0,0,0,0};

    }else{

        tr[u].l=l,tr[u].r=r;

        int mid=(l+r)>>1;

        build(u<<1,l,mid);

        build(u<<1|1,mid+1,r);

        pushup(u);

    }

}

node query(int u,int l,int r){

    int ll=tr[u].l,rr=tr[u].r;

    if(ll>=l&&rr<=r){

        return tr[u];

    }

    node res;

    int mid=(ll+rr)>>1;

    if(r<=mid){

        res=query(u<<1,l,r);

    }else if(l>mid){

        res=query(u<<1|1,l,r);

    }else {//一定要讨论清楚,

        node left=query(u<<1,l,r);

        node right=query(u<<1|1,l,r);

        res.sum=tr[u<<1].sum+tr[u<<1|1].sum;

        res.lc=max(left.lc,left.sum+right.lc);

        res.rc=max(right.rc,right.sum+left.rc);

        res.maxc=max(left.maxc,max(right.maxc,left.rc+right.lc));

    }

    return res;

}

void modify(int u,int x,int v){

    int ll=tr[u].l,rr=tr[u].r;

    if(ll==x&&rr==x){

        tr[u]={x,x,v,v,v,v};

    }else {

        int mid=(ll+rr)>>1;

        if(mid>=x)modify(u<<1,x,v);

        else modify(u<<1|1,x,v);

        pushup(u);

    }

}

int main(){

    scanf("%d%d",&n,&m);

    build(1,1,n+2);

    for(int i=1;i<=n;i++){

        int tmp;

        scanf("%d",&tmp);

        modify(1,i,tmp);

    }

    for(int i=0;i<m;i++){

        int k,x,y;

        scanf("%d%d%d",&k,&x,&y);

        if(k==1){

            if(x>y)swap(x,y);

            printf("%d\n",query(1,x,y).maxc);

        }else {

            modify(1,x,y);

        }

    }

}


http://www.ppmy.cn/devtools/172302.html

相关文章

CentOS 7 如何挂载ntfs的移动硬盘

CentOS 7 如何挂载ntfs的移动硬盘 前言一、查看硬盘并尝试挂载(提示无法挂载)二、yum安装epel-release提示yum被锁定三、强行终止yum的进程四、yum安装epel-release完成五、yum安装ntfs-3g六、此时可正常挂载NTFS硬盘 前言 CentOS 7默认情况下是不支持NTFS的文件系统&#xff…

TraeAI结合Proteus实现AI编程并仿真一个复杂工业物联网控制系统的开发(视频)

简介&#xff1a; 本视频聚焦基于国内首个 AI 原生集成开发环境&#xff08;AI IDE&#xff09;的 AI 编程实践。借助提示词&#xff0c;在 Proteus 环境下结合 ESP32 - S3&#xff0c;运用 MicroPython 进行状态机程序设计。展示如何通过在Trae CN人工智能集成编程开发环境&am…

深入理解 Android Intent:Action 与 Category 详解

在 Android 开发中&#xff0c;Intent 是组件之间通信的核心机制&#xff0c;其中 Action&#xff08;动作&#xff09;和 Category&#xff08;类别&#xff09;决定了 Intent 的用途和目标。在本文中&#xff0c;我们将详细解析常见的 Action 和 Category 及其应用场景&#…

StarRocks 中 CURRENT_TIMESTAMP 和 CURRENT_TIME 分区过滤问题

背景 本文基于Starrocks 3.3.5 最近在进行Starrocks 跑数据的时候&#xff0c;发现了一个SQL 扫描了所有分区的数据&#xff0c;简化后的SQL如下&#xff1a; select date_created from tableA where date_createddate_format(current_time(), %Y-%m-%d %H:%i:%S) limit 20其…

Redis | 基于 Redis 实现机器列表 Token 缓存的 Java 实现

关注&#xff1a;CodingTechWork 引言 在分布式系统中&#xff0c;Token 缓存是一种常见的需求。它可以帮助我们快速验证用户身份&#xff0c;减少对数据库的频繁访问&#xff0c;提高系统的性能和响应速度。本文将介绍如何使用 Redis 来实现机器列表的 Token 缓存&#xff0c…

Axure RP9.0教程: 多级联动【设置选项改变时->情形->面板状态】(给动态面板元件设置相关交互事件的情形,来控制其他面板不同的状态。)

文章目录 引言I 多级联动(省、市、区)实现思路添加三省、市、区下拉列表给省下拉框添加数据源将市、区下拉框添加不同状态,分别以省、市命名给省下拉控件设置选项改变时的交互事件省下拉控件的交互事件情形市下拉交互事件的配置II 知识扩展: 展示省 → 地级市 → 区县的多级…

springboot 四层架构之间的关系整理笔记二

Spring Boot 的四层架构就像班级里的 ‌4 个小组‌&#xff0c;分工合作完成一个大任务&#xff01;&#xff08;比如组织一场运动会&#xff09; ‌1. 控制层&#xff08;Controller&#xff09;—— 像「传达室门卫」‌ ‌做什么‌&#xff1a;专门和“外面的人”说话&#…

leetcode48.旋转图像

思路源于 【大厂程序员带你刷力扣】【LeetCode 48】旋转图像 从4*4矩阵开始&#xff0c;将顶层放到右侧&#xff0c;右侧放到下层&#xff0c;下层放到左侧&#xff0c;再对中间的2*2重复这个操作&#xff0c;以此类推 class Solution {public void rotate(int[][] matrix) {i…