树状数组 之扩展

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

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

单点查询,区间修改

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

区间修改 [l,r] [ l , r ] 时只改变了 a[l1],a[l] a [ l − 1 ] , a [ l ] 的差和 a[r],a[r+1] a [ r ] , a [ r + 1 ] 的差,修改 l,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<=n) {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[i1] c [ i ] = s [ i ] − s [ i − 1 ]

query(c,x)=c[x]+c[x1]++c[1] q u e r y ( c , x ) = c [ x ] + c [ x − 1 ] + ⋯ + c [ 1 ]

假设要查询 xi=1s[i] ∑ i = 1 x s [ i ] ,需要的是

s[x]+s[x1]+s[x2]++s[1] s [ x ] + s [ x − 1 ] + s [ x − 2 ] + ⋯ + s [ 1 ]

等价于
c[x]+2c[x1]++nc[1] c [ x ] + 2 ∗ c [ x − 1 ] + ⋯ + n ∗ c [ 1 ]

那么如果新维护一个 d[i]=ic[i] d [ i ] = i ∗ c [ i ] 的数组,那么查询它能够得到

query(d,x)=xc[x]+(x1)c[x1]++c[1] q u e r y ( d , x ) = x ∗ c [ x ] + ( x − 1 ) ∗ c [ x − 1 ] + ⋯ + c [ 1 ]

显然 xquery(c,x)query(d.x)=s[x]+s[x1]+s[x2]++s[1]=xi=1s[i] x ∗ q u e r y ( c , x ) − q u e r y ( d . x ) = s [ x ] + s [ x − 1 ] + s [ x − 2 ] + ⋯ + s [ 1 ] = ∑ 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) q u e r y ( x , y ) 等同于查询 (1,1)(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) ( x , y ) 这个点应该放的是

s[x][y](query(x1,y)+query(x,y1)+query(x1,y1)) s [ x ] [ y ] − ( q u e r y ( x − 1 , y ) + q u e r y ( x , y − 1 ) + q u e r y ( x − 1 , y − 1 ) )
s[x][y]s[x1][y]s[x][y1]+s[x1][y1] s [ x ] [ y ] − s [ x − 1 ] [ y ] − s [ x ] [ y − 1 ] + s [ x − 1 ] [ y − 1 ]

观察发现每次修改 (x,y) ( x , y ) ,影响的是区间 (x,y)(n,m) ( x , y ) − ( n , m )
所以若需区间修改 (x1,y1)(x2,y2) ( x 1 , y 1 ) − ( x 2 , y 2 ) ,相当于修改 4 4 个位置(x1,y1),(x2+1,y1),(x1,y2+1),(x2+1,y2+1)
每个位置应该修改的值为 (add,add,add,add) ( a d d , − a d d , − a d d , a d d )

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[i1][j]s[i][j1]+s[i1][j1] c [ i ] [ j ] = s [ i ] [ j ] − s [ i − 1 ] [ j ] − s [ i ] [ j − 1 ] + s [ i − 1 ] [ j − 1 ]

query(x,y)=i=1xj=1yc[i][j] q u e r y ( x , y ) = ∑ i = 1 x ∑ j = 1 y c [ i ] [ j ]

假设要查询 xi=1yj=1s[i][j] ∑ i = 1 x ∑ j = 1 y s [ i ] [ j ] ,需要的是

i=1xj=1yc[i][j](x+1i)(y+1j) ∑ i = 1 x ∑ j = 1 y c [ i ] [ j ] ∗ ( x + 1 − i ) ∗ ( y + 1 − j )

等价于
(x+1)(y+1)i=1xj=1yc[i][j] ( x + 1 ) ∗ ( y + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y c [ i ] [ j ]

(y+1)i=1xj=1yc[i][j]i − ( y + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y c [ i ] [ j ] ∗ i

(x+1)i=1xj=1yc[i][j]j − ( x + 1 ) ∗ ∑ i = 1 x ∑ j = 1 y c [ i ] [ j ] ∗ j

+i=1xj=1yc[i][j]ij + ∑ i = 1 x ∑ j = 1 y c [ i ] [ j ] ∗ i ∗ j

所以说只需要维护四个二维树状数组 c[i][j],c[i][j]i,c[i][j]j,c[i][j]ij 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值