1. 程式人生 > >hdu2642-二維樹狀陣列 單點更新 區間查詢

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<x2,y1<y2)
//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 <bits/stdc++.h>
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;
}