1264. 动态求连续区间和

给定 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
输入样例:

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(n),和原始的数组大小一样
时间复杂度:在某一点加值logN,查询某个区间logN
1.每个结点tr[x] 保存的是区间 (x-lowbit(x),x] 的和
2.每个结点tr[x]的父结点是tr[x+lowbit(x)],当一个结点所管理的区间发生改变,就会影响到其父结点,复杂度logN
3.修改操作只能是在某个点+一个值,而不能是直接改变,所要改变可以换成加差值,tr[x] += v-c(v要变成的值,c原来的值)
4.树状数组可以解决的问题都可以用线段树解决,这两者的区别在哪里呢?树状数组的系数要少很多,就比如字符串模拟大数可以解决大数问题,也可以解决1+1的问题,但没人会在1+1的问题上用大数模拟

#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e6+10;

int N,M;
int tr[maxn];

int lowbit(int x){
    return x&-x; //返回只保留二进制的最后一个1之后的值
}
void add(int idx,int x){ //向下标为idx的元素+x,同时更新其所影响结点的前缀和
    for(int i = idx;i<=N;i += lowbit(i)) //这里的N是数组最后一个下标,有时候不一定是N
        tr[i] += x;
}

int query(int idx){ //求下标为idx的前缀和
    int sum = 0;
    for(int i = idx;i>=1;i -= lowbit(i))
        sum += tr[i];
    return sum;
}

int main(){
    cin>>N>>M;
    int tmp;
    for(int i = 1;i<=N;i++){
        scanf("%d",&tmp);
        add(i,tmp);
    }
    int op,x,y;
    while(M--){
        scanf("%d%d%d",&op,&x,&y);
        if(op == 1) add(x,y);
        else printf("%d\n",query(y)-query(x-1));//前缀和的差 = 区间的和
    }

    return 0;
}