【洛谷】P3368 【模板】树状数组 2

题目地址:

https://www.luogu.com.cn/problem/P3368

题目描述:
如题,已知一个数列,你需要进行下面两种操作:将某区间每一个数数加上 x x x;求出某一个数的值。

输入格式:
第一行包含两个整数 N N N M M M,分别表示该数列数字的个数和操作的总个数。第二行包含 N N N个用空格分隔的整数,其中第 i i i个数字表示数列第 i i i项的初始值。接下来 M M M行每行包含 2 2 2 4 4 4个整数,表示一个操作,具体如下:
操作 1 1 1:格式:1 x y k含义:将区间 [ x , y ] [x,y] [x,y]内每个数加上 k k k
操作 2 2 2:格式:2 x含义:输出第 x x x个数的值。

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

数据规模与约定:
对于 30 % 30\% 30%的数据: N ≤ 8 , M ≤ 10 N≤8,M≤10 N8,M10
对于 70 % 70\% 70%的数据: N ≤ 10000 , M ≤ 10000 N≤10000,M≤10000 N10000,M10000
对于 100 % 100\% 100%的数据: 1 ≤ N , M ≤ 500000 1≤N,M≤500000 1N,M500000 1 ≤ x , y ≤ n 1≤x,y≤n 1x,yn,保证任意时刻序列中任意元素的绝对值都不大于 2 30 2^{30} 230

这两个操作容易让人想到差分数组,因为差分数组对操作1的效率很高,是 O ( 1 ) O(1) O(1)。例如对于数组 [ 1 , 5 , 4 , 2 , 3 ] [1,5,4,2,3] [1,5,4,2,3],我们构造差分数组: [ 1 , 4 , − 1 , − 2 , 1 ] [1,4,-1,-2,1] [1,4,1,2,1]假设我们要将原数组的第2到第4个数每个数都加2,得 [ 1 , 7 , 6 , 4 , 3 ] [1,7,6,4,3] [1,7,6,4,3]对于差分数组来说,只有第2和第5个数变了(因为原数组只有第2个数和第1个数的差,以及第5与第4的差变了),我们只需要将差分数组的第2个数加2,第5个数减2即可: [ 1 , 6 , − 1 , − 2 , − 1 ] [1,6,-1,-2,-1] [1,6,1,2,1]但差分数组对操作2的复杂度则是 O ( n ) O(n) O(n),我们需要将差分数组从开头直到第 i i i个数的和算出来,也就是要算差分数组的前缀和。对于前缀和,树状数组(Fenwick Tree)有着不错的效率,它支持 O ( log ⁡ n ) O(\log n) O(logn)的更新和查询。对树状数组的详细介绍,请看https://blog.csdn.net/qq_46105170/article/details/103870987。思路就很简单了:
1、对原数组的差分数组建Fenwick Tree
2、操作1对应着让Fenwick Tree的第 a a a个数加 x x x,并对第 b + 1 b+1 b+1个数减 x x x
3、操作2对应着查差分数组前 a a a个数前缀和,可以利用Fenwick Tree查

代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 5e5 + 10;
int n, m;
int tr[N], a[N];

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

void add(int x, int v) {
    while (x <= n) {
        tr[x] += v;
        x += lowbit(x);
    }
}

int sum(int x) {
    int res = 0;
    while (x) {
        res += tr[x];
        x -= lowbit(x);
    }

    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] - a[i - 1]);

    while (m--) {
        int type, x, y, k;
        scanf("%d", &type);
        if (type == 1) {
            scanf("%d%d%d", &x, &y, &k);
            add(x, k), add(y + 1, -k);
        } else {
            scanf("%d", &x);
            printf("%d\n", sum(x));
        }
    }

    return 0;
}

预处理时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),每次询问 O ( log ⁡ n ) O(\log n) O(logn),空间 O ( n ) O(n) O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值