二维树状数组总结及模板

由于最近经常被二维问题卡住,而且二维线段树日常写炸,于是来学习总结一下二维树状数组来缓解一下一遇到二维问题就拉闸的情况。

首先是最基本的单点修改+区间查询

这个好说,就是普通一维的一个小拓展,就直接上代码了:

int lowbit(int x)
{
    return x&-x;
}
void add(int x,int y,int v)
{
    while(x<=n)
    {
        int ty=y;
        while(ty<=n)
            tree[x][ty]+=v,ty+=lowbit(ty);
        x+=lowbit(x);
    }
}
int ask(int x,int y)
{
    int res=0;
    while(x)
    {
        int ty=y;
        while(ty)
            res+=tree[x][ty],ty-=lowbit(ty);
        x-=lowbit(x);
    }
    return res;
}

 

接下来是区间修改+单点查询

在思考这个问题以前,首先思考一维树状数组如何进行区间修改+单点查询

显然,通过一维差分可以解决这个问题

设原数组为a[i], 设数组d[i]=a[i]-a[i-1](a[0]=0),则a[i]=\sum_{j=1}^{i}d[j],可以通过求d[i]的前缀和查询

当给区间[l,r]加上x的时候,a[l]与前一个元素 a[l-1]的差增加了x,a[r+1]与 a[r]的差减少了x。根据d[i]数组的定义,只需给a[l]加上 x, 给 a[r+1]减去x即可

代码如下:

void add(int x,int v)
{
    while(x<=n) 
        sum[x]+=v,x+=lowbit(x);
}
void real_add(int l,int r,int v)
{
    add(l,x);
    add(r+1,-x);
}
int ask(int x)
{ 
    int res=0;
    while(x)
        res+=sum[x],x-=lowbit(x);
    return res;
}

 

那么同理,二维的区间修改+单点查询可以用类似二维差分的方法来解决

二维前缀和:sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]

我们可以令差分数组d[i][j]表示a[i][j]与 a[i-1][j]+a[i][j-1]-a[i-1][j-1]的差。

代码如下:

void add(int x,int y,int v)
{
    while(x<=n)
    {
        int ty=y;
        while(ty<=n)
            tree[x][ty]+=v,ty+=lowbit(ty);
        x+=lowbit(x);
    }
}
void real_add(int x1,int y1,int x2,int y2,int v)
{
    add(x1,y1,v);
    add(x1,y2+1,-v);
    add(x2+1,y1,-v);
    add(x2+1,y2+1,v);
}
int ask(int x, int y)
{
    int res=0;
    while(x)
    {
        int ty=y;
        while(ty)
            res+=tree[x][ty],ty-=lowbit(ty);
        x-=lowbit(x);
    }
    return res;
}

 

最后思考如何区间修改+区间查询:

同样首先思考一维树状数组如何进行区间修改+区间查询

基于上文的差分思路,我们知道位置p的前缀和为 \sum_{i=1}^{p}a[i]=\sum_{i=1}^{p}\sum_{j=1}^{i}d[j]

在等式最右侧的式子\sum_{i=1}^{p}\sum_{j=1}^{i}d[j]中,d[1]被用了p次,d[2]被用了p-1次……那么我们可以得出:

位置p的前缀和为 \sum_{i=1}^{p}\sum_{j=1}^{i}d[j]=\sum_{i=1}^{p}d[i]*(p-i+1)=(p+1)*\sum_{i=1}^{p}d[i]-\sum_{i=1}^{p}d[i]*i

于是我们可以维护两个数组的前缀和:
一个数组是 sum1[i]=d[i]
另一个数组是 sum2[i]=d[i]*i

位置p的前缀和即:(p+1)*sum1数组中p的前缀和 - sum2数组中p的前缀和。

区间[l, r]的和即:位置r的前缀和 - 位置l的前缀和。

代码如下:

void add(int x,int v)
{
    int p=x;
    while(x<=n)
    {
        sum1[x]+=v;
        sum2[x]+=v*p;
        x+=lowbit(x);
    }
}
void real_add(int l,int r,int v)
{
    add(l,v),
    add(r+1,-v);
}
int ask(int x)
{
    int res=0;
    int p=x;
    while(x)
    {
        res+=(p+1)*sum1[x]-sum2[x];
        x-=lowbit(x);
    }
    return res;
}
int real_ask(int l,int r)
{
    return ask(r)-ask(l-1);
}

 

同理,二维的区间修改+区间查询也可以用类似的思路来解决

类比之前一维数组的区间修改区间查询,下面这个式子表示的是点(x, y)的二维前缀和:\sum_{i=1}^{x}\sum_{j=1}^{y}\sum_{h=1}^{i}\sum_{k=1}^{j}d[h][k]

 

首先,类比一维数组,统计一下每个d[h][k]出现过多少次。d[1][1]出现了x*y次,d[1][2]出现了x*(y-1)次……d[h][k]出现了(x-h+1)*(y-k+1) 次。

那么这个式子就可以写成:

\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*(x+1-i)*(y+1-j)

把这个式子展开,就得到:

(x+1)*(y+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]-(y+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*i-(x+1)*\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*j+\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]*i*j

那么我们要开四个树状数组,分别维护:

d[i][j],d[i][j]*i,d[i][j]*j,d[i][j]*i*j

这样就可以解决上述问题了

代码如下:

void add(int x,int y,int v)
{
    for(int i=x;i<=n;i+=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j))
        {
            t1[i][j]+=v;
            t2[i][j]+=v*x;
            t3[i][j]+=v*y;
            t4[i][j]+=v*x*y;
        }
}
void real_add(int x1,int y1,int x2,int y2,int v)
{
    add(x1,y1,v);
    add(x1,y2+1,-v);
    add(x2+1,y1,-v);
    add(x2+1,y2+1,v);
}
int ask(int x, int y)
{
    int res=0;
    for(int i=x;i;i-=lowbit(i))
        for(int j=y;j;j-=lowbit(j))
            res+=(x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x+1)*t3[i][j]+t4[i][j];
    return res;
}
int real_ask(int x1,int y1,int x2,int y2)
{
    return ask(x2,y2)-ask(x2,y1-1)-ask(x1-1,y2)+ask(x1-1,y1-1);
}

 

 

你可能感兴趣的:(acm)