算法学习系列(三十六):树状数组与线段树

news2024/10/7 14:31:02

目录

  • 引言
  • 一、树状数组
    • 1.概念
    • 2.代码模板
    • 3.例题
      • 动态求连续区间和
      • 数星星
  • 二、线段树
    • 1.概念
    • 2.代码模板
    • 3.例题
      • 动态求连续区间
      • 数列区间最大值

引言

在算法竞赛当中,这个树状数组和线段树用的还是比较多的,树状数组是用来动态的求前缀和的,而线段树的功能是包含树状数组的,相当于一个大砍刀,什么都能做,而且出题点也是比较多的,所以得好好对待好好学啊。


一、树状数组

1.概念

功能:能够动态的求前缀和
时间复杂度 l o g N logN logN ,修改和查询都是
思路: t r [ i ] tr[i] tr[i] 存的是 ( x − l o w b i t ( x ) , x ] (x-lowbit(x),x] (xlowbit(x),x]的和,相当于只存了一段的前缀和,然后查询和修改只把一段又一段的前缀和一修改就行了,只用修改 l o g N 个 logN个 logN ,而不用修改 N 个 N个 N
在这里插入图片描述

lowbit(x):x的二进制数末尾有几个零
树状数组大概就长如下图的样子:
在这里插入图片描述

2.代码模板

const int N = 1e5+10;

int n, m;
int w[N], tr[N];

int lowbit(int x)
{
    return x & -x;
}

void add(int x, int c)  //修改的话减去原来的加上现在的即可
{
    for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int query(int x)  //求x的前缀和
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

3.例题

动态求连续区间和

思路:模板题

题目描述:

给定  n  个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。

输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。
第二行包含 n个整数,表示完整数列。
接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。
数列从 1 开始计数。

输出格式
输出若干行数字,表示 k=0 时,对应的子数列 [a,b] 的连续和。

数据范围
1≤n≤100000,1≤m≤100000,1≤a≤b≤n,
数据保证在任何时候,数列中所有元素之和均在 int 范围内。

输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35

示例代码:

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 1e5+10;

int n, m;
int w[N], tr[N];

int lowbit(int x)
{
    return x & -x;
}

void add(int x, int c)
{
    for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}

int query(int x)  //查询x的前缀和
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    for(int i = 1; i <= n; ++i) add(i,w[i]);
    
    while(m--)
    {
        int k, a, b;
        scanf("%d%d%d", &k, &a, &b);
        if(k) add(a,b);
        else printf("%d\n", query(b) - query(a-1));  //前缀和思想
    }
    
    return 0;
}

数星星

思路:这个其实问左下方的星星有多少个就是几级的,由于输入是按从下往上从左往右的顺序输入,所以到该星星的时候,不会有比它还上的了,所以只需要判断之前谁的x比它小于等于就行了,可以抽象为一个数组,每个数组的下标i表示当前横坐标为i的星星有几个,要求的等级就是该下标的前缀和,又因为相当是动态插入的星星所以用树状数组比较好。

题目描述:

天空中有一些星星,这些星星都在不同的位置,每个星星有个坐标。

如果一个星星的左下方(包含正左和正下)有 k 颗星星,就说这颗星星是 k 级的。

例如,上图中星星 5 是 3 级的(1,2,4 在它左下),星星 2,4 是 1 级的。

例图中有 1 个 0 级,2 个 1 级,1 个 2 级,1个 3 级的星星。

给定星星的位置,输出各级星星的数目。

换句话说,给定 N 个点,定义每个点的等级是在该点左下方(含正左、正下)的点的数目,试统计每个等级有多少个点。

输入格式
第一行一个整数 N,表示星星的数目;
接下来 N 行给出每颗星星的坐标,坐标用两个整数 x,y 表示;
不会有星星重叠。星星按 y 坐标增序给出,y 坐标相同的按 x 坐标增序给出。

输出格式
N 行,每行一个整数,分别是 0 级,1 级,2 级,……,N−1 级的星星的数目。

数据范围
1≤N≤15000,0≤x,y≤32000
输入样例:
5
1 1
5 1
7 1
3 3
5 5
输出样例:
1
2
1
1
0

示例代码:

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 32010;

int n;
int tr[N], res[N];  //res[i]代表第i级的数量

int lowbit(int x)
{
    return x & -x;
}

void add(int x, int c)
{
    for(int i = x; i < N; i += lowbit(i)) tr[i] += c;
}

int query(int x)
{
    int res = 0;
    for(int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        x++;  //因为x是从0开始的,而我们需要x从1开始
        res[query(x)]++;
        add(x,1);
    }
    
    for(int i = 0; i < n; ++i) printf("%d\n", res[i]);
    
    return 0;
}

二、线段树

1.概念

思想:也是用二分的策略,把一个有性质的值存在多个区间段中,然后通过下标二分来找或者修改
用途:可以快速求前缀和、区间最大值/最小值等多种用法
如下图为线段树的一个示例图:
在这里插入图片描述

2.代码模板

const int N = 1e5+10;

int n, m;
int w[N];

struct Node
{
    int l, r, sum;
}tr[N * 4];  //因为结点个数有n个,由于pushup时最底层的结点需特判,所以再开了一层

void pushup(int u)  //更新结点u
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;  //左右儿子之和
}

void build(int u, int l, int r)
{
    if(l == r) tr[u] = {l, r, w[r]};
    else
    {
        tr[u] = {l, r};  //c++11没有的默认0
        int mid = l + r >> 1;  //这里是因为底下穿的就是按mid划分的,也必须划分
        build(u << 1, l, mid), build(u << 1 | 1, mid+1, r);
        pushup(u);  //递归的过程
    }
}

int query(int u, int l, int r)
{
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;  //相当于取包含的意思,区间是按mid已经划分好了,不重不漏
    int mid = tr[u].l + tr[u].r >> 1;  
    int sum = 0;
    if(l <= mid) sum = query(u << 1, l, r);
    if(r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

void modify(int u, int x, int v)  //给x下标加上v
{
    if(tr[u].l == tr[u].r) tr[u].sum += v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if(x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);  //递归更新
    }
}

3.例题

动态求连续区间

思路:模板题

题目描述:


给定 n 个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。

输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。
第二行包含 n 个整数,表示完整数列。
接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a 个数加 b)。

数列从 1 开始计数。

输出格式
输出若干行数字,表示 k=0 时,对应的子数列 [a,b] 的连续和。

数据范围
1≤n≤100000,1≤m≤1000001≤a≤b≤n,
数据保证在任何时候,数列中所有元素之和均在 int 范围内。

输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35

示例代码:

#include <cstdio>
#include <iostream>

using namespace std;

const int N = 1e5+10;

int n, m;
int w[N];

struct Node
{
    int l, r, sum;
}tr[N * 4];  //因为结点个数有n个,由于pushup时最底层的结点需特判,所以再开了一层

void pushup(int u)  //更新结点u
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;  //左右儿子之和
}

void build(int u, int l, int r)
{
    if(l == r) tr[u] = {l, r, w[r]};
    else
    {
        tr[u] = {l, r};  //c++11没有的默认0
        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(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if(l <= mid) sum = query(u << 1, l, r);
    if(r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

void modify(int u, int x, int v)  //给x下标加上v
{
    if(tr[u].l == tr[u].r) tr[u].sum += v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if(x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);  //递归更新
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    
    build(1, 1, n);
    
    while(m--)
    {
        int k, a, b;
        scanf("%d%d%d", &k, &a, &b);
        if(k) modify(1, a, b);
        else printf("%d\n", query(1, a, b));
    }
    
    return 0;
}

数列区间最大值

思路:把sum变成maxv就行了,思路跟上一题是一样的。

题目描述:

输入一串数字,给你 M 个询问,每次询问就给你两个数字 X,Y,要求你说出 X 到 Y 这段区间内的最大数。

输入格式
第一行两个整数 N,M 表示数字的个数和要询问的次数;
接下来一行为 N 个数;接下来 M 行,每行都有两个整数 X,Y。

输出格式
输出共 M 行,每行输出一个数。

数据范围
1≤N≤105,1≤M≤106,1≤X≤Y≤N,数列中的数字均不超过231−1
输入样例:
10 2
3 2 4 5 6 8 1 2 9 7
1 4
3 8
输出样例:
5
8

示例代码:

#include <cstdio>
#include <iostream>
#include <climits>

using namespace std;

const int N = 1e5+10;

int n, m;
int w[N];

struct Node
{
    int l, r, maxv;
}tr[N * 4];

void pushup(int u)
{
    tr[u].maxv = max(tr[u << 1].maxv, tr[u << 1 | 1].maxv);
}

void build(int u, int l, int r)
{
    if(l == r) tr[u] = {l,r,w[r]};
    else
    {
        tr[u] = {l,r};
        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(tr[u].l >= l && tr[u].r <= r) return tr[u].maxv;
    int mid = tr[u].l + tr[u].r >> 1;
    int maxv = INT_MIN;
    if(l <= mid) maxv = query(u << 1, l, r);
    if(r > mid) maxv = max(maxv, query(u << 1 | 1, l, r));
    return maxv;
}

int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%d", &w[i]);
    
    build(1,1,n);
    
    while(m--)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%d\n", query(1,a,b));
    }
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1460076.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP WebSocket:技术解析与实用指南

本文旨在帮助初学者掌握在PHP中使用WebSocket的关键概念和技术。我们将深入讨论从建立连接、绑定到监听等各方面的操作&#xff0c;并提供易于理解和实践的指导。 一、socket协议的简介 WebSocket是什么&#xff0c;有什么优点 WebSocket是一个持久化的协议&#xff0c;这是…

Echarts图例如何将选中与未选中状态配置成不同图形

背景 使用Echarts实现功能过程中&#xff0c;由于用户感觉Echarts图例的原生图案(例如圆形)不能直观地表现出该处可以点击筛选展示&#xff0c;故设计将选中的图例与未选中的图例设置成两种不同的图形(多为勾选与未勾选)。Echarts原生功能可以配置图例图案&#xff0c;但无法直…

电脑进水无法开机怎么办 电脑进水开不了机的解决方法

意外总是会不定时打破你的计划&#xff0c;电脑这类电器最怕遇到的除了火还有水&#xff0c;设备进水会导致数据丢失&#xff0c;那么我们遇到电脑进水怎么办&#xff1f;进水之后不正确处理也会引起很多不必要的麻烦. 解决办法 第一步&#xff1a;关机 如果您的电脑是在开…

探索NFC技术在游戏玩具娱乐,医疗保健和穿戴设备领域的三大应用

NFC是与众不同的无线技术。这意味着它只能在两个设备相近时起作用。在其他用无线技术随机广播的方式以被接收时&#xff0c;NFC更重要的独特之处于其使用电源的方式。或者&#xff0c;更确切地说&#xff0c;它可以在不供电的环境下进行工作。它是一种非接触式智能卡技术的演进…

威尔金森功分器基本原理学习笔记

威尔金森功分器基本原理 威尔金森功率分配器的功能是将输入信号等分或不等分的分配到各个输出端口&#xff0c;并保持相同输出相位。环形器虽然有类似功能&#xff0c;但威尔金森功率分配器在应用上具有更宽的带宽。微带形功分器的电路结构如图所示&#xff0c;其中&#xff0…

小程序--loading和toast

一、loading wx.showLoading({})显示loading提示框。wx.hideLoading({})隐藏loading提示框。 title&#xff1a;文字提示内容 mask&#xff1a;是否显示透明蒙层&#xff0c;防止触摸穿透。 更多属性参考showLoading官方文档。 wx.showLoading({title: 加载中...,mask: true }…

全球自然灾害数据可视化分享

分享自然灾害数据&#xff0c;主要包括地震、火山、山体滑坡、饥荒和干旱、飓风、龙卷风和旋风、极端降水和洪水、极端温度(冷热)、森林大火、闪电等。 想获取全球历年自然灾害数据&#xff0c;关注本后台私信“自然灾害数据”&#xff0c;即可获得&#xff0c;长期有效&#…

C# CAD交互界面-模态窗体与非模态窗体调用方式

运行环境Visual Studio 2022 c# cad2016 一、模态窗体调用方式&#xff1a; 当一个模态窗体打开时&#xff0c;它会阻塞主窗体的所有输入&#xff0c;直到关闭该模态窗体为止。例如&#xff0c;弹出一个对话框让用户必须完成某些操作后才能继续使用主程序。 [CommandMethod(&q…

Bert基础(二)--多头注意力

多头注意力 顾名思义&#xff0c;多头注意力是指我们可以使用多个注意力头&#xff0c;而不是只用一个。也就是说&#xff0c;我们可以应用在上篇中学习的计算注意力矩阵Z的方法&#xff0c;来求得多个注意力矩阵。让我们通过一个例子来理解多头注意力层的作用。以All is well…

设计模式四:适配器模式

1、适配器模式的理解 适配器模式可以理解为有两个现成的类Adaptee和Target&#xff0c;它们两个是不能动的&#xff0c;要求必须使用B这个类来实现一个功能&#xff0c;但是A的内容是能复用的&#xff0c;这个时候我们需要编写一个转换器 适配器模式 Adaptee&#xff1a;被适…

ubuntu 22.04.3 live server安装JDK21与远程编程环境和maven

ubuntu 22.04.3 live server安装JDK21与远程编程环境 一、安装jdk21 解压jdk压缩包&#xff0c;命令&#xff1a; tar -zxvf jdk-21_linux-x64_bin.tar.gz打开环境变量&#xff0c;命令&#xff1a; sudo vim /etc/profile配置环境变量 export JAVA_HOME/root/jdk-21.0.2 …

【Python】OpenCV-图片差异检测与标注

图片差异检测与标注 在图像处理领域中&#xff0c;检测两张图片之间的差异是一项重要的任务。本文将介绍一个使用OpenCV库进行图片差异检测的简单示例代码&#xff0c;并详细注释每个步骤。 1. 引言 图片差异检测是在两张图片之间寻找差异点或区域的过程。这项技术可用于监测…

缀点成线

1232. 缀点成线 给定一个数组 coordinates &#xff0c;其中 coordinates[i] [x, y] &#xff0c; [x, y] 表示横坐标为 x、纵坐标为 y 的点。请你来判断&#xff0c;这些点是否在该坐标系中属于同一条直线上。 示例 1&#xff1a; 输入&#xff1a;coordinates [[1,2],[2,3…

分治算法总结(Java)

目录 分治算法概述 快速排序 练习1&#xff1a;排序数组 练习2&#xff1a;数组中的第K个最大元素 练习3&#xff1a;最小k个数 归并排序 练习4&#xff1a;排序数组 练习5&#xff1a;交易逆序对的总数 练习6&#xff1a;计算右侧小于当前元素的个数 练习7&#xff1…

Maven setting.xml 配置

目的&#xff1a;可以把我们书写的jar包发布到maven私有仓库&#xff0c;简称私仓 1. 打开云效 2.点击 非生产库-snapshot mave release仓库与snapshot仓库区别&#xff1f; 在软件开发中&#xff0c;"Maven release 仓库"和"Maven snapshot 仓库"是两种…

一周学会Django5 Python Web开发-Django5路由定义

锋哥原创的Python Web开发 Django5视频教程&#xff1a; 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计22条视频&#xff0c;包括&#xff1a;2024版 Django5 Python we…

Docker vs VM

关于应用程序的托管和开发&#xff0c;市场中的技术和产品琳琅满目。对比 Docker 和 VM&#xff0c;如何取舍&#xff1f;这主要由自身团队的因素决定&#xff0c;在选择 Docker 的情况下&#xff0c;你需要保证程序可在容器和虚拟机中运行。另外&#xff0c;成本和易用性也是重…

五种多目标优化算法(MOGWO、MOJS、NSWOA、MOPSO、MOAHA)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 1.1MOGWO 1.2MOJS 1.3NSWOA 1.4MOPSO 1.5MOAHA 二、5种多目标优化算法性能对比 为了测试5种算法的性能将其求解9个多目标测试函数&#xff08;zdt1、zdt2 、zdt3、 zdt4、 zdt6 、Schaffer、 Kursawe 、Viennet2、 Viennet3&#xff09;&#xff0…

C语言字符串函数strcpy与strncpy

注意&#xff1a; 这两个函数的功能&#xff0c;都是将 src 中的字符串&#xff0c;复制到 dest 中。strcpy() 没有边界控制&#xff0c;因此可能会由于 src 的过长而导致内存溢出。strncpy() 有边界控制&#xff0c;最多复制 n1 个字符&#xff08;其中最后一个是 ‘\0’ &…

百度RT-DETR :基于视觉变换器的实时物体检测器

概述 实时检测转换器 (RT-DETR) 由百度开发&#xff0c;是一种尖端的端到端物体检测器&#xff0c;可在保持高精度的同时提供实时性能。它利用视觉转换器&#xff08;ViT&#xff09;的强大功能&#xff0c;通过解耦尺度内交互和跨尺度融合&#xff0c;高效处理多尺度特征。RT…