蓝桥杯第十三讲--树状数组与线段树【例题】(一)

简介: 蓝桥杯第十三讲--树状数组与线段树【例题】

前言

蓝桥杯官网:蓝桥杯大赛——全国大学生TMT行业赛事

✨本博客讲解 蓝桥杯C/C++ 备赛所涉及算法知识,此博客为第十三讲:树状数组与线段树【例题】

本篇博客所包含习题有:

👊动态求连续区间和

👊数星星

👊数列区间最大值


树状数组与线段树【习题】见博客:蓝桥杯第十三讲–树状数组与线段树【习题】


注: 蓝桥杯对于树状数组与线段树的考察十分的基础简单,且历年只考察过两道题目,读者不需要了解其原理,只需要背诵模板学会调用即可。如果想要知道具体实现方式,见博客:树状数组线段树(一)线段树(二) 【尚未更新】


博客内容以题代讲,通过讲解题目的做法来帮助读者快速理解算法内容,需要注意:学习算法不能光过脑,更要实践,请读者务必自己敲写一遍本博客相关代码!!!


动态求连续区间和

题目要求

题目描述:

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

输入格式:

image.png

输出格式:

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

数据范围:

image.png

数据保证在任何时候,数列中所有元素之和均在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

思路分析

本题用 树状数组 以及 线段树 两种方法去写,同时介绍模板

树状数组

使用树状数组会让时间复杂度变为:O(logn)树状数组作用有两个:

  • 让某个位置上的数加上一个数(单点修改)
  • 求一个前缀和(区间查询)


当然,树状数组还有其他功能,但是对于蓝桥杯而言,只需要掌握以上两种即可

树状数组虽然是一维的,但是我们可以把它理解为是二维的,类似树的结构,如下图所示:

17.png

这里有几个需要注意的点:

image.png

以上就是对树状数组的一些定义说明,读者 不需要 知道为什么这么定义以及其中细节,感兴趣的可以看博客:树状数组【尚未更新】,里面会有讲解。

下面介绍树状数组的几个基本函数:

int lowbit(int x)
{
    return x & -x;
}
void add(int x, int v)      // 往 x 的位置加上一个数字 v
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += v;
}
int query(int x)           // 求 [1, x] 原数组的前缀和
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

上述代码需要:背背背!!!

代码(树状数组)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000010;
int n, m;
int a[N], tr[N];
int lowbit(int x)
{
    return x & -x;
}
void add(int x, int v)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += v;
}
int query(int 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", &a[i]);
    for (int i = 1; i <= n; i ++ ) add(i, a[i]);
    while(m -- )
    {
        int k, x, y;
        scanf("%d%d%d", &k, &x, &y);
        if (k == 0) printf("%d\n", query(y) - query(x - 1));
        else add(x, y);
    }
    return 0;
}

线段树

使用树状数组会让时间复杂度变为:O(logn),线段树的作用特别之多,包含树状数组的所有功能并且有很多额外的功能,但是代码相较与树状数组而言比较冗长。

线段树可以简单的理解成一个二叉树:

18.png

即线段树是对一段的数组,每次均分为两份,一直均分直到不能均分,红线代表的是求和的过程。

原理不进行讲解,读者 不需要 知道为什么这么定义以及其中细节,感兴趣的可以看博客:线段树(一)线段树(二)【尚未更新】,里面会有讲解。

下面介绍线段树的几个函数:

struct Node
{
    int l, r;    // 涵盖范围,如上图中的蓝色字体就是l,r的取值
    int sum;
}tr[4 * N];      // 需要开题目要求的4倍大小,不需要知道其原理
void pushup(int u)        // 动态维护线段树
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int u, int l, int r)   // 建一个线段树,u为根,l和r代表两个子树的涵盖范围
{
    if (l == r) tr[u] = {l, r, w[l]};
    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)    // 查询操作,求[l, 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);
    }
}

代码(线段树)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int n, m;
int w[N];
struct Node
{
    int l, r;
    int sum;
}tr[4 * N];
void pushup(int 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[l]};
    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].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)
{
    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);
    int k, a, b;
    while (m -- )
    {
        scanf("%d%d%d", &k, &a, &b);
        if (k == 0) printf("%d\n", query(1, a, b));
        else modify(1, a, b);
    }
    return 0;
}


目录
相关文章
|
3月前
|
算法 Java C语言
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-8 算法训练 操作格子 线段树
第十四届蓝桥杯集训——练习解题阶段(无序阶段)-ALGO-8 算法训练 操作格子 线段树
29 0
|
8月前
|
存储 算法
蓝桥杯:递归 与 例题:斐波那契数列及优化与应用
蓝桥杯:递归 与 例题:斐波那契数列及优化与应用
50 0
|
8月前
蓝桥杯:递推 例题:数字三角型问题
蓝桥杯:递推 例题:数字三角型问题
26 0
|
8月前
|
移动开发 Shell
蓝桥杯:2020 国赛 例题:天干地支
蓝桥杯:2020 国赛 例题:天干地支
45 0
|
8月前
蓝桥杯:2019 国赛 例题:求值
蓝桥杯:2019 国赛 例题:求值
35 0
|
8月前
蓝桥杯:桶排序 与 例题:算式问题
蓝桥杯:桶排序 与 例题:算式问题
45 0
|
8月前
蓝桥杯:Map 和 例题:弗里的语言
蓝桥杯:Map 和 例题:弗里的语言
42 0
|
8月前
蓝桥杯:队列 Queue 和 例题: CLZ 的银行
蓝桥杯:队列 Queue 和 例题: CLZ 的银行
42 0
|
8月前
蓝桥杯:vector 与 例题:快递分拣
蓝桥杯:vector 与 例题:快递分拣
45 0
|
8月前
|
机器学习/深度学习
蓝桥杯:栈 和 例题 :小邋遢的衣橱
蓝桥杯:栈 和 例题 :小邋遢的衣橱
97 0