树状数组

树状数组

树状数组是一种支持单点修改和区间查询的,代码量小 (这是重点) 的数据结构。

事实上,树状数组能解决的问题是线段树能解决的问题的子集:树状数组能做的,线段树一定能做;线段树能做的,树状数组不一定可以。然而,树状数组的代码要远比线段树短,时间效率常数也更小,因此仍有学习价值。
有时,在差分数组和辅助数组的帮助下,树状数组还可解决更强的 区间加单点值 和 区间加区间和 问题。

树状数组工作原理

\(t[x]\) 管辖一段左边界是 \(x-lowbit(x)+1\)、右边界是 \(x\) 的区间总信息(如图)

基本操作

\(lowbit\)

\(x\) 的二进制所有位全部取反,再加 \(1\),就可以得到 \(-x\) 的二进制编码

设原先 \(x\) 的二进制编码是 \((...)10...00\),全部取反后得到 \([...]01...11\), 加 \(1\) 后得到 \([...]10...00\),也就是 \(-x\) 的二进制编码了。这里 \(x\) 二进制表示中第一个 \(1\)\(x\) 最低位的 \(1\)

\((...)\)\([...]\) 中省略号的每一位分别相反,所以 \(x \& -x = (...)10...00 \& [...]10...00 = 10...00\),得到的结果就是 \(lowbit\)

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

\(update\)

对于修改 \(x\),只需要修改 \(x\) 会影响的 \(\log x\)\(t_i\) 即可

il void update(int x,cs int k){
    while(x<=r) tr[x]+=k,x=lowbit(x);
}

\(query\)

如图,对于查询 \(x\) 的前缀和:
\(t[x]\) 开始往前跳,有 \(c[x]\) 管辖 \(a[x-\operatorname{lowbit}(x)+1 \ldots x]\)
\(x \gets x - \operatorname{lowbit}(x)\),如果 \(x = 0\) 说明已经跳到尽头了,终止循环;否则回到第一步。
将跳到的 \(t\) 合并。

il int query(int x){
    ri int s=0;
    while(x) s+=tr[x],x=lowbit(x);
    return x;
}

单点加&区间和

区间和用一下前缀和思想就好了

struct tree{
    int tr[N],r;
    il int lowbit(cs int x){return x&(-x);}
    il void update(int x,cs int k){while(x<=r) tr[x]+=k,x+=lowbit(x);}
    il int query(int x){ri int s=0;while(x) s+=tr[x],x-=lowbit(x);return s;}    
    il int ask(int l,int r){if(l>r) swap(l,r);return query(r)-query(l-1);}
}t;

signed main(){
    ri int n=rd(),m=rd();t.r=n;
    for(ri int i=1;i<=n;++i) t.update(i,rd());
    for(ri int i=1,op,l,r;i<=m;++i){
        op=rd(),l=rd(),r=rd();
        op==1?t.update(l,r),0:(wt(t.ask(l,r)),putchar(10));
    }
    return 0;
}

区间加&单点查

考虑差分,对于差分数组 \(d\)\(a[x]\sum_{i=1}^x d[i]\)
区间 \([l,r]\)\(k\) 相当于 \(d[l]+k\)\(d[r+1]-k\)
查询单点 \(x\) 相当于求 \(d[x]\) 的前缀和

struct tree{
    int tr[N],r;
    il int lowbit(cs int x){return x&(-x);}
    il void update(int x,cs int k){while(x<=r) tr[x]+=k,x+=lowbit(x);}
    il void operate(int l,int r,int k){update(l,k),update(r+1,-k);}
    il int query(int x){ri int s=0;while(x) s+=tr[x],x-=lowbit(x);return s;}  
}t;

signed main(){
    ri int n=rd(),m=rd();t.r=n;
    for(ri int i=1;i<=n;++i) a[i]=rd();
    for(ri int i=1;i<=n;++i) t.update(i,a[i]-a[i-1]);
    for(ri int i=1,op,l,r,k;i<=m;++i){
        if((op=rd())==2) wt(t.query(rd())),putchar(10);
        else l=rd(),r=rd(),k=rd(),t.operate(l,r,k);
    }
    return 0;
}

区间加&区间和

考虑差分&前缀和,对于差分数组 \(d\)\(a[x]\sum_{i=1}^x d[i]\)
于是,对于\(x\)的前缀和 \(s[x]\)

\[\begin{align*} s[x] &= \sum_{i=1}^x a[i] \\ &= \sum_{i=1}^x \sum_{j=1}^i d[i] \\ &= \sum_{i=1}^x (x-i+1) \cdot d[i] \\ &= \sum_{i=1}^x (x+1) \cdot d[i]-i \cdot d[i] \end{align*} \]

开两颗树状数组,分别维护 \(\sum_{i=1}^x (x+1) \cdot d[i]\)\(\sum_{i=1}^x i \cdot d[i]\) 即可

struct tree{
    int tr[N],r;
    il int lowbit(cs int x){return x&(-x);}
    il void update(int x,cs int k){while(x<=r) tr[x]+=k,x+=lowbit(x);}
    il int query(int x){ri int s=0;while(x) s+=tr[x],x-=lowbit(x);return s;}  
}t1,t2;
il void update(int l,int r,int k){
    t1.update(l,k),t1.update(r+1,-k),t2.update(l,k*l),t2.update(r+1,-k*(r+1));
}
il int ask(int l,int r){
    return (r+1)*t1.query(r)-l*t1.query(l-1)-t2.query(r)+t2.query(l-1);
}

矩形区域加&区域和(二维树状数组)

考虑二维差分&前缀和

对于差分数组 \(d[i][j]=a[i][j]+a[i-1][j-1]-a[i-1][j]-a[i][j-1]\)

对于其二维前缀和\(s[x][y]\)

\[\begin{align*} s[x][y] = &\sum_{i=1}^x \sum_{j=1}^y \sum_{i'=1}^i \sum_{j'=1}^j d[i'][j'] \\ = &\sum_{i=1}^x \sum_{i'=1}^i \sum_{j=1}^y \sum_{j'=1}^j d[i'][j'] \\ = &\sum_{i=1}^x \sum_{i'=1}^i \sum_{j=1}^y (y+1-j) d[i'][j] \\ = &\sum_{j=1}^y (y+1-j) \cdot \sum_{i=1}^x \sum_{i'=1}^i d[i'][j] \\ = &\sum_{j=1}^y (y+1-j) \cdot \sum_{i=1}^x (x+1-i) \cdot d[i][j] \\ = &\sum_{i=1}^x \sum_{j=1}^y (x+1-i) \cdot (y+1-j) \cdot d[i][j] \\ = &\sum_{i=1}^x \sum_{j=1}^y (x+1) \cdot (y+1) \cdot d[i][j] + \sum_{i=1}^x \sum_{j=1}^y i \cdot j \cdot d[i][j] \\ & - \sum_{i=1}^x \sum_{j=1}^y (x+1) \cdot j \cdot d[i][j] - \sum_{i=1}^x \sum_{j=1}^y (y+1) \cdot i \cdot d[i][j] \end{align*} \]

开四颗树状数组

\(t1\) 维护 \(\sum_{i=1}^x \sum_{j=1}^y (x+1) \cdot (y+1) \cdot d[i][j]\)

\(t2\) 维护 \(\sum_{i=1}^x \sum_{j=1}^y i \cdot j \cdot d[i][j]\)

\(t3\) 维护 \(\sum_{i=1}^x \sum_{j=1}^y i \cdot d[i][j]\)

\(t4\) 维护 \(\sum_{i=1}^x \sum_{j=1}^y j \cdot d[i][j]\)

struct tree{
    int tr[N][N];
    il int lowbit(cs int x){return x&(-x);}
    il void update(int x,int y,int k){
        for(ri int i=x;i<=n;i+=lowbit(i)){
            for(ri int j=y;j<=m;j+=lowbit(j)) tr[i][j]+=k;
        } return;
    }
    il int query(int x,int y){
        ri int as=0;
        for(ri int i=x;i;i-=lowbit(i)){
            for(ri int j=y;j;j-=lowbit(j)) as+=tr[i][j];
        } return as;
    }
}t1,t2,t3,t4;
il void update(int x,int y,int k){
    t1.update(x,y,k),t2.update(x,y,k*x*y);
    t3.update(x,y,k*x),t4.update(x,y,k*y);
}
il void Update(int x,int y,int xx,int yy,int k){
    update(x,y,k),update(xx+1,yy+1,k);
    update(x,yy+1,-k),update(xx+1,y,-k);
}
il int query(int x,int y){
    return t1.query(x,y)*(x+1)*(y+1)+t2.query(x,y)
      -t3.query(x,y)*(y+1)-t4.query(x,y)*(x+1);
}
il int Query(int x,int y,int xx,int yy){
    return query(x-1,y-1)+query(xx,yy)-query(x-1,yy)-query(xx,y-1);
}

edit

posted @ 2023-03-27 20:06  雨夜风月  阅读(16)  评论(0编辑  收藏  举报