树状数组小结

树状数组小结

背景

树状数组本质是区间前缀和,但是众所周知,暴力和前缀和各有优缺点……

(图片中本来是线段树的,但是其实差不多吧)

所以诞生了树状数组这个东西。

功能

树状数组分为以下几步

声明部分

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <set>
#include <queue>
#include <vector>
#define IL inline
#define re register
#define LL long long
using namespace std;

IL LL read() {
    LL ans = 0;
    bool fu = 0;
    char ch = getchar();
    while ((ch > '9' || ch < '0') && ch != '-') ch = getchar();
    if (ch == '-')
        fu = 1, ch = getchar();
    while (ch <= '9' && ch >= '0') ans = (ans << 3) + (ans << 1) + (ch ^ 48), ch = getchar();
    if (fu)
        ans *= -1;
    return ans;
}
LL n, m;
LL a[1000010];
LL s[1000010];
LL b[1000010];

建树

IL void add(LL x, LL y) {
    for (; x <= n; x += x & (-x)) a[x] += y;
}
int main()
{
    n = read();
    m = read();
    for (re int i = 1; i <= n; i++){
        add(i,read());
}    

使用了add函数,见下面的单点修改。

当然这是简单的O(nlogn)建树,还有更快的:

更快的建树

    n = read();
    m = read();
    for (re int i = 1; i <= n; i++){
        b[i]=read();
        a[i]+=b[i];
        a[i+(i&-i)]+=a[i];
    }    

时间复杂度为O(n)

单点修改

IL void add(LL x, LL y) {
    for (; x <= n; x += x & (-x)) a[x] += y;
}

查询前缀和

IL LL ask(LL x) {
    re LL ans = 0;
    for (; x; x -= x & (-x)) ans += a[x];
    return ans;
}

由此可以查询区间。

Code

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <set>
#include <queue>
#include <vector>
#define IL inline
#define re register
#define LL long long
using namespace std;

IL LL read() {
    LL ans = 0;
    bool fu = 0;
    char ch = getchar();
    while ((ch > '9' || ch < '0') && ch != '-') ch = getchar();
    if (ch == '-')
        fu = 1, ch = getchar();
    while (ch <= '9' && ch >= '0') ans = (ans << 3) + (ans << 1) + (ch ^ 48), ch = getchar();
    if (fu)
        ans *= -1;
    return ans;
}
LL n, m;
LL a[1000010];
LL s[1000010];
LL b[1000010];
IL void add(LL x, LL y) {
    for (; x <= n; x += x & (-x)) a[x] += y;
}
IL LL ask(LL x) {
    re LL ans = 0;
    for (; x; x -= x & (-x)) ans += a[x];
    return ans;
}
int main() {
    n = read();
    m = read();
    for (re int i = 1; i <= n; i++){
        b[i]=read();
        a[i]+=b[i];
        a[i+(i&-i)]+=a[i];
    }    
    LL t, x, y;
    while (m--) {
        t = read();
        x = read();
        y = read();
        if (t == 1)
            add(x, y);
        else
            cout << ask(y) - ask(x - 1) << endl;
    }

    return 0;
}

小结

这应该是最快的RSQ算法了。如果这都过不去请参见zkw线段树……

(我还真就遇到过……)

 1 #include<cstdio>
 2 #define ll long long
 3 #define go(i,j,n,k) for(ll i=j;i<=n;i+=k)
 4 #define fo(i,j,n,k) for(ll i=j;i>=n;i-=k)
 5 #define mn 1000010
 6 inline ll read(){ll x=0,f=1;char ch=getchar();while(ch>'9'||ch<'0'){if(ch=='-')f=-f;ch=getchar();    }
 7                 while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
 8 ll z[mn << 2], M, n, m;
 9 inline void update(ll rt){z[rt] = z[rt<<1] + z[rt<<1|1];}
10 inline void build(){for(M=1;M<n+2;M<<=1);go(i,M+1,M+n,1)z[i]=read();fo(i,M,1,1) update(i);}
11 inline void modify(ll now,ll v){for(z[now+=M]+=v,now>>=1;now;now>>=1)update(now);}
12 inline ll query(ll l,ll r){ll ans=0;for(--l+=M,++r+=M;l^r^1;l>>=1,r>>=1){if(~l&1)ans+=z[l^1];if(r&1)ans+=z[r^1];}return ans;}
13 int main(){
14     n=read(),m=read();build();
15     go(i,1,m,1){
16         int s=read(),x=read(),y=read();
17         if(s==1)modify(x,y);else printf("%lld\n",query(x,y));
18     }
19 }
zkw

树状数组的拓展

 

事实上,树状数组存在许多拓展。

比如最值维护,只要只需要查询前缀最值,且求改只涉及取最值操作,也是可以做的。

而树状数组可以区间修改,这里不进行讨论。

树状数组可以和主席树搭配使用,超出noip范围,也不讨论。

posted @ 2020-09-18 21:59  Vanilla_chan  阅读(220)  评论(0编辑  收藏  举报