树状数组模板

196 阅读2分钟

P3374 【模板】树状数组 1 - 洛谷

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 xx

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,m(1n,m5105)n,m(1\le n, m\le 5\cdot 10^5),分别表示该数列数字的个数和操作的总个数。

第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。

接下来 mm 行每行包含 33 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 xx 个数加上 kk

  • 2 x y 含义:输出区间 [x,y][x,y] 内每个数的和

数据保证对于任意时刻,aa 的任意子区间(包括长度为 11 和 nn 的子区间)和均在 [231,231)[−2^{31},2^{31}) 范围内。

输出格式

输出包含若干行整数,即为所有操作 22 的结果。

样例 #1

样例输入 #1

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

样例输出 #1

14
16

题目分析

首先我们思考一下暴力解答方式。

每次 O(1)O(1) 修改数据,但是需要 O(n)O(n) 统计区间和,最终复杂度为 O(nm)O(nm),这是不可接受的。

接下来,我们介绍一种数据结构——树状数组,树状数组可以抽象为一棵树, cc 的每一个 cic_i 表示 ailowbit(i)+1aia_{i-lowbit(i)+1} \sim a_i 的加和, 它支持 O(logn)O(logn) 修改和 O(logn)O(logn) 查找前缀。

对于修改操作,若修改位置为 xx,则我们需要同时修改当前节点以上所有父节点的值,即所有包含当前位置的范围。

对于查询操作,若查询位置为下标 xx 的所有前缀和,则我们需要同时相加所有在当前 cxc_x 表示范围前的范围值。

具体操作见代码。

Accept代码

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

const int N = 500010;

int n, m;
int a[N], c[N];

void modify(int x, int k)
{
    for ( ; x <= n; x += x & (-x)) c[x] += k;
}

int query(int x)
{
    int s = 0;
    for ( ; x; x -= x & (-x)) s += c[x];
    return s;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m;
    for (int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        modify(i, a[i]);
    }

    for (int i = 0; i < m; i ++)
    {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 1)
        {
            modify(x, y);
        }
        else
        {
            cout << query(y) - query(x - 1) << "\n";
        }
    }
    return 0;
}