【算法】回顾:树状数组 Level Up

请注意,本文编写于 221 天前,最后修改于 79 天前,其中某些信息可能已经过时。

在上一篇博客中,讲解了一些关于树状数组基础的部分以及其最简单的用法:区间查询,单点修改,还没有看过的请戳这里

细心的童鞋可能已经发现树状数组的查询是前缀和的查询,于是可以利用这个形式扩展出很多其他的用法
比如说

单点查询,区间修改

其查询前缀和的性质,直接用树状数组维护一个差分数组,那么每次查询得到差分和前$n$项,即$a[n]$

区间修改$[l,r]$时只改变了$a[l-1],a[l]$的差和$a[r],a[r+1]$的差,修改$l,r+1$位置即可

void insert(int x,int add) {
    while(x<=n) {a[x]+=add;x+=lowbit(x);}
}
int query(int x) {
    int ans=0;
    while(x) {ans+=a[x];x-=lowbit(x);}
    return ans;
}

其修改查询操作与基本方法完全一致,但是维护的东西不同

for(int i=1;i<=n;++i) s[i]=read();
for(int i=1;i<=n;++i) insert(i,s[i]-s[i-1]);

修改时:(以区间加为例)

l=read();r=read();add=read();
insert(l,add);insert(r+1,-add);

区间查询,区间修改

大部分认为,树状数组仅仅能单点查询,区间修改或者是区间查询,单点修改,但是这样就显得很naive.
其实很简单的,就能使树状数组资瓷区间查询,区间修改

显然,既然要区间修改,那么前缀和没跑了
另外,为了区间查询,我们来分析一下每次查询都能得到些什么
假设$c[i]=s[i]-s[i-1]$

$$query(c,x)=c[x]+c[x-1]+\cdots+c[1]$$

假设要查询$\sum_{i=1}^{x}s[i]$,需要的是
$$s[x]+s[x-1]+s[x-2]+\cdots+s[1]$$
等价于
$$c[x]+2*c[x-1]+\cdots+n*c[1]$$

那么如果新维护一个$d[i]=i*c[i]$的数组,那么查询它能够得到
$$query(d,x)=x*c[x]+(x-1)*c[x-1]+\cdots+c[1]$$

显然$x*query(c,x)-query(d.x)=s[x]+s[x-1]+s[x-2]+\cdots+s[1]=\sum_{i=1}^{x}s[i]$

得解
同时为了优化把两个数组的修改查询放一起

void insert(int x,int add) {
    int pos=x;
    while(x<=n) {a[x]+=add;b[x]+=add*pos;x+=lowbit(x);}
}
int query(int x) {
    int ans=0,pos=x;
    while(x) {ans+=a[x]*pos;ans-=b[x];x-=lowbit(x);}
    return ans;
}

二维树状数组

搞定序列,考虑一下在平面中的问题
由于树状数组的查询等同于查询前缀和,那么定义$query(x,y)$等同于查询$(1,1)-(x,y)$子矩阵的和

其实就相当于树状数组套树状数组了(雾

void insert(int x,int y,int add) {
    if(x>n || y>n) return;
    while(x<=n) {
        int newy=y;
        while(newy<=n) {a[x][newy]+=add;a[x][newy]%=2;newy+=lowbit(newy);}
        x+=lowbit(x);
    }
}
int query(int x,int y) {
    int ans=0;
    while(x) {
        int newy=y;
        while(newy) {ans+=a[x][newy];ans%=2;newy-=lowbit(newy);}
        x-=lowbit(x);
    }
    return ans;
}

单点查询,区间修改

老办法,维护差分数组
由于每次查询的到的是一个矩阵的和,那么我们在$(x,y)$这个点应该放的是$$s[x][y]-(query(x-1,y)+query(x,y-1)+query(x-1,y-1))$$即$$s[x][y]-s[x-1][y]-s[x][y-1]+s[x-1][y-1]$$

观察发现每次修改$(x,y)$,影响的是区间$(x,y)-(n,m)$
所以若需区间修改$(x_1,y_1)-(x_2,y_2)$,相当于修改$4$个位置$(x_1,y_1),(x_2+1,y_1),(x_1,y_2+1),(x_2+1,y_2+1)$
每个位置应该修改的值为$(add,-add,-add,add)$

int x1=read(),y1=read(),x2=read(),y2=read(),add=read();
insert(x1,y1,add);insert(x2+1,y2+1,add);
insert(x1,y2+1,-add);insert(x2+1,y1,-add);

区间查询,区间修改

类比一维的情况下,考虑每次查询得到些什么
假设$c[i][j]=s[i][j]-s[i-1][j]-s[i][j-1]+s[i-1][j-1]$

$$query(x,y)=\sum_{i=1}^{x}\sum_{j=1}^{y}c[i][j]$$

假设要查询$\sum_{i=1}^{x}\sum_{j=1}^{y}s[i][j]$,需要的是
$$\sum_{i=1}^{x}\sum_{j=1}^{y}c[i][j]*(x+1-i)*(y+1-j)$$
等价于
$$(x+1)*(y+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}c[i][j]$$
$$-(y+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}c[i][j]*i$$
$$-(x+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}c[i][j]*j$$
$$+\sum_{i=1}^{x}\sum_{j=1}^{y}c[i][j]*i*j$$

所以说只需要维护四个二维树状数组$c[i][j],c[i][j]*i,c[i][j]*j,c[i][j]*i*j$即可

void insert(int x,int y,int add) {
    int newx=x;
    while(newx<=n) {
        int newy=y;
        while(newy<=n) {
            a[0][newx][newy]+=add;
            a[1][newx][newy]+=add*x;
            a[2][newx][newy]+=add*y;
            a[3][newx][newy]+=add*x*y;
            newy+=lowbit(newy);
        }
        newx+=lowbit(newx);
    }
}
int query(int x,int y) {
    int newx=x,ans=0;
    while(newx) {
        int newy=y;
        while(newy) {
            ans+=(x+1)*(y+1)*a[0][newx][newy];
            ans-=(y+1)*a[1][newx][newy];
            ans-=(x+1)*a[2][newx][newy];
            ans+=a[3][newx][newy];
            newy-=lowbit(newy);
        }
        newx-=lowbit(newx);
    }
    return ans;
}
Comments

添加新评论