树状数组常见应用

一、单点修改、区间查询

例: 已知一个数列,有两种操作:
1、将某一个数加上x
2、求出某区间每一个数的和

P3374 【模板】树状数组 1

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;

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

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;
    }
}

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

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> tt;
    for(int i = 1; i <= n; i ++) {
        cin >> a[i];
        add(i, a[i]);
    }
    
    while(tt --) {
        int op;
        cin >> op;
        
        if(op == 1) {
            int x, k;
            cin >> x >> k;
            add(x, k);
            
        } else {
            int l, r;
            cin >> l >> r;
            
            cout << sum(r) - sum(l - 1) << '\n';
        }
    }
    
    return 0;
}

二、区间修改、单点查询

例: 已知一个数列,有两种操作:
1、将某区间每一个数加上x
2、求出某一个数的值

P3368 【模板】树状数组 2

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

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

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;
    }
}

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

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> tt;
    for(int i = 1; i <= n; i ++) {
        cin >> a[i];
        add(i, a[i] - a[i - 1]);
    }
    
    while(tt --) {
        int op;
        cin >> op;
        
        if(op == 1) {
            int l, r, x;
            cin >> l >> r >> x;
            
            add(l, x);
            add(r + 1, -x);
        } else {
            int x;
            cin >> x;
            
            cout << sum(x) << '\n';
        }
    }
    
    return 0;
}

三、区间修改、区间查询(需要额外的辅助数组)

例: 已知一个数列,有两种操作:
1、将某区间每一个数加上x
2、求出某区间每一个数的和

P3372 【模板】线段树 1

计算推导

b 1 _1 1 = a 1 _1 1
b 2 _2 2 = a 2 _2 2 - a 1 _1 1

b n _n n = a n _n n - a n − 1 _{n-1} n1

数组a的前缀和: S = a 1 _1 1 + a 2 _2 2 + … + a n _n n

a 0 _0 0 = 0
a 1 _1 1 = b 1 _1 1
a 2 _2 2 = b 1 _1 1 + b 2 _2 2
a 3 _3 3 = b 1 _1 1 + b 2 _2 2 + b 3 _3 3

a n _n n = b 1 _1 1 + b 2 _2 2 + b 3 _3 3 + … + b n _n n

S = ∑ 1 n \sum_1^n 1na i _i i = (n + 1) * (b 1 _1 1 + b 2 _2 2 + … + b n _n n) - (1 * b 1 _1 1 + 2 * b 2 _2 2 + … + n * b n _n n)
即S = (n + 1) * ∑ 1 n \sum_1^n 1nb i _i i - ∑ 1 n \sum_1^n 1ni * b i _i i

可用 tr1 维护 b i _i i 的前缀和,tr2维护 i * b i _i i 的前缀和
则在代码中S可表示为(x + 1) * sum(tr1, x) - sum(tr2, x)

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5 + 10;
LL a[N], tr1[N], tr2[N];
int n, tt;

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

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

LL sum(LL tr[], int x) {
    LL res = 0;
    for(int i = x; i > 0; i -= lowbit(i)) {
        res += tr[i];
    }
    
    return res;
}

LL presum(LL x) {
    return (x + 1) * sum(tr1, x) - sum(tr2, x);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n >> tt;
    for(int i = 1; i <= n; i ++) {
        cin >> a[i];
        LL u = a[i] - a[i - 1];
        
        add(tr1, i, u);
        add(tr2, i, (LL) i * u);
    }
    
    while(tt --) {
        int op;
        cin >> op;
        
        if(op == 2) {
            int l, r;
            cin >> l >> r;
            
            cout << presum(r) - presum(l - 1) << '\n';
        } else {
            int l, r, c;
            cin >> l >> r >> c;
            
            add(tr1, l, c);
            add(tr1, r + 1, -c);
            
            add(tr2, l, l * c);
            add(tr2, r + 1, (r + 1) * -c);
        }
    }
    
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值