hdu2642-二维树状数组 单点更新 区间查询

我之前已经把一维的树状数组都写了,接下来我来写一下二维的树状数组。其实二维的树状数组和一维的没有本质和差别,可以说就是扩展了一维,其余一样。

来看看二维树状数组单点更新、区间查询的问题:
就是一个矩阵,进行两种操作。
1. 对矩阵里的某个数加上一个数
2. 查询某个子矩阵里所有数字的和

我们看看树状数组是怎么扩展到二维的。和一维一样,设a[][],c[][],我们看看这时候c[][]的组成。
设原始二维数组为:
A[][]={{a11,a12,a13,a14,a15,a16,a17,a18,a19},
{a21,a22,a23,a24,a25,a26,a27,a28,a29},
{a31,a32,a33,a34,a35,a36,a37,a38,a39},
{a41,a42,a43,a44,a45,a46,a47,a48,a49}};

记:
B[1]={a11,a11+a12,a13,a11+a12+a13+a14,a15,a15+a16,…} 这是第一行的一维树状数组
B[2]={a21,a21+a22,a23,a21+a22+a23+a24,a25,a25+a26,…} 这是第二行的一维树状数组
B[3]={a31,a31+a32,a33,a31+a32+a33+a34,a35,a35+a36,…} 这是第三行的一维树状数组
B[4]={a41,a41+a42,a43,a41+a42+a43+a44,a45,a45+a46,…} 这是第四行的一维树状数组

那么:
C[1][1]=a11,C[1][2]=a11+a12,C[1][3]=a13,C[1][4]=a11+a12+a13+a14,c[1][5]=a15,C[1][6]=a15+a16,… 这是A[][]第一行的一维树状数组
C[2][1]=a11+a21,C[2][2]=a11+a12+a21+a22,C[2][3]=a13+a23,C[2][4]=a11+a12+a13+a14+a21+a22+a23+a24,C[2][5]=a15+a25,C[2][6]=a15+a16+a25+a26,… 这是A[][]数组第一行与第二行相加后的树状数组
C[3][1]=a31,C[3][2]=a31+a32,C[3][3]=a33,C[3][4]=a31+a32+a33+a34,C[3][5]=a35,C[3][6]=a35+a36,…这是A[][]第三行的一维树状数组
C[4][1]=a11+a21+a31+a41,C[4][2]=a11+a12+a21+a22+a31+a32+a41+a42,C[4][3]=a13+a23+a33+a43,… 这是A[][]数组第一行+第二行+第三行+第四行后的树状数组

仔细看一下,如果你对描述一维树状数组最经典的那个图很熟悉的话应该立刻就能看出来了。
二维数组的规律就是,不管是横坐标还是纵坐标,将他们单独拿出来,他们都符合x += lowbit(x),属于它的父亲节点,即都符合那个经典的图。比如C[4][2]:
单独看横坐标(就是把纵坐标去掉,重复的只算一个):C[4] = C[2]+C[3]+a[4] (经典图得出)= a[1]+a[2]+a[3]+a[4] (从上面公式得出)
单独看纵坐标(就是把横坐标去掉,重复的只算一个):C[2] = C[1]+a[2] (经典图得出)= a[1]+a[2] (上面公式得出)

还不明白就多看几遍,体会一下,应该就懂了。

区间查询的话,调用一次query(x,y)显然是求(1,1) 到 (x,y)范围内矩形的和。观察下图,我们没办法一次查询出来给定区间的值,但是我们可以通过计算得出。
现在如果要查询蓝色范围内的和
调用A点是求紫色边框
调用B点是求绿色边框
调用C点是求黄色边框
调用D点是求红色边框
那么A-B-C+D即是答案。
这里写图片描述

贴一下模板:

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

void update(int x,int y,int value)
{
    for(int i=x;i<=maxn;i+=lowbit(i))
        for(int j=y;j<=maxn;j+=lowbit(j))
            sz[i][j] += value;
}

int query(int x,int y)
{
    int ans = 0;
    for(int i=x;i>0;i-=lowbit(i))
        for(int j=y;j>0;j-=lowbit(j))
            ans += sz[i][j];
    return ans;
}

//比如我们要查询x1,y1,x2,y2这个矩形区间的和(x1
//query(x2,y2)-query(x1-1,y2)-query(x2,y1-1)+query(x1-1,y1-1)

接下来以hdu2642为模板题来写一下二维树状数组的单点更新,区间查询。
题意:一个星空,二维的。上面有1000*1000的格点,每个格点上有星星在闪烁。一开始时星星全部暗淡着,有M个操作:
B x y 点亮一盏星星
D x y 熄灭一盏星星
Q x1 x2 y1 y2 查询这个矩形里面亮着的星星的个数。

题解:这就是个二维树状数组单点更新、区间查询的模板题,直接写即可。不过有几个需要注意的地方。就像我在一维里说的,树状数组下标是从1开始维护的,所以我们要把数据偏移到下标从1开始,二维里也是一样。这里坐标可能为0,所以我们把每个坐标都++,然后就是点亮的星星不能重复点亮,暗淡的星星不能重复暗淡,设一个状态的数组即可。

代码:

#include 
using namespace std;

const int maxn = 1005;
int sz[maxn][maxn],status[maxn][maxn];

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

void update(int x,int y,int value)
{
    for(int i=x;i<=maxn;i+=lowbit(i))
        for(int j=y;j<=maxn;j+=lowbit(j))
            sz[i][j] += value;
}

int query(int x,int y)
{
    int ans = 0;
    for(int i=x;i>0;i-=lowbit(i))
        for(int j=y;j>0;j-=lowbit(j))
            ans += sz[i][j];
    return ans;
}

int main()
{
    int T;
    int x1,x2,y1,y2;
    char op;
    memset(sz,0,sizeof(sz));
    memset(status,0,sizeof(status));
    scanf("%d",&T);
    while(T--)
    {
        getchar();
        scanf("%c",&op);
        switch(op)
        {
        case 'B':
            scanf("%d%d",&x1,&y1);
            x1++,y1++;
            if(status[x1][y1]==0) //星星暗淡才能更新
            {
                update(x1,y1,1);
                status[x1][y1]=1;
            }
            break;
        case 'D':
            scanf("%d%d",&x1,&y1);
            x1++,y1++;
            if(status[x1][y1]>0) //星星点亮才能更新
            {
                update(x1,y1,-1);
                status[x1][y1]=0;
            }
            break;
        case 'Q':
            scanf("%d%d%d%d",&x1,&x2,&y1,&y2);
            x1++,x2++,y1++,y2++;
            if(x1>x2) swap(x1,x2); //把x1置为较小的那个
            if(y1>y2) swap(y1,y2); //把y1置为较小的那个
            printf("%d\n",query(x2,y2)-query(x2,y1-1)-query(x1-1,y2)+query(x1-1,y1-1));  //像图一样A-B-C+D得答案
            break;
        }
    }
    return 0;
}

参考博客:
http://128kj.iteye.com/blog/1746732
http://blog.csdn.net/qq_18661257/article/details/47347995

你可能感兴趣的:(ACM,hdu,树状数组)