1. 程式人生 > 實用技巧 >二維樹狀陣列及(不會用到的)三維樹狀陣列

二維樹狀陣列及(不會用到的)三維樹狀陣列

二維樹狀陣列及(不會用到的)三維樹狀陣列

前置芝士

一維樹狀陣列(lowbit)

二維樹狀陣列

二維樹狀陣列涉及到兩種基本操作,修改矩陣中的一個點,查詢子矩陣的和

首先是修改點的操作:

void update(int x,int y,int z){		//座標為(x,y)的點增加z
	for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=n;j+=lowbit(j))
        c[i][j]+=z;
}

然後是查詢子矩陣的和,這裡查詢的是從左上角到目標點所形成的矩陣的元素和

int sum(int x,int y){
    int ret=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
        ret+=c[i][j];
    return ret;
}

那麼如果我要查具體的一個子矩陣,就需要給出左上角的點和右下角的點的座標,然後:

int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
cout<<sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)<<endl;

就可以了

下面附上完整的二維樹狀陣列的程式碼:

#include<iostream>
using namespace std;
const int maxn=1005;
const int maxm=1005;
int n,m;
int q;
int a[maxn][maxm];
int c[maxn][maxm];
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int y,int z)
{
    for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=m;j+=lowbit(j))
        c[i][j]+=z;
}

int sum(int x,int y)
{
    int ret=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
        ret+=c[i][j];
    return ret;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        cin>>a[i][j];
        update(i,j,a[i][j]);
    }
    cin>>q;
    while(q--)
    {
        int x;
        cin>>x;
        if(x==1)
        {
            int y,z,w;
            cin>>y>>z>>w;
            update(y,z,w);
        }
        if(x==2)
        {
            int x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            cout<<sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)<<endl;
        }
    }
    return 0;
}

接下來我們對二維樹狀陣列進行簡單的拓展,將其拓展為修改矩形區間,查詢單點的二維樹狀陣列

其實就是把二維差分的思想引入進去,當然,如果不用樹狀陣列直接用二維差分陣列也是完全可以的,這個時候修改區間變成了O(1),查詢點就變成了O(n),還是需要自己去權衡

二維樹狀陣列的修改和查詢的函式還是完全不用去變的

修改區間就要這麼修改了:

void add(int x1,int y1,int x2,int y2,int w)
{
    update(x1,y1,w);            
	update(x2+1,y2+1,w);
	update(x2+1,y1,-w);
	update(x1,y2+1,-w);
}

這個東西雖然是類比一維情況得來的,但是你不要去想,去在紙上畫一畫,主對角線端點為正,負對角線端點為負,然後就很顯然了

查詢單點的話直接sum(x,y)即可

這裡給出完整的程式碼:

#include<iostream>
using namespace std;
const int maxn=1005;
const int maxm=1005;
int n,m;
int q;
int a[maxn][maxm];
int d[maxn][maxm];
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int y,int z)
{
    for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=m;j+=lowbit(j))
        d[i][j]+=z;
}
void add(int x1,int y1,int x2,int y2,int w)
{
    update(x1,y1,w);
    update(x2+1,y2+1,w);
    update(x2+1,y1,-w);
    update(x1,y2+1,-w);
}
int sum(int x,int y)
{
    int ret=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
        ret+=d[i][j];
    return ret;
}
int main()
{
    cin>>n>>m;
    cin>>q;
    while(q--)
    {
        int x;
        cin>>x;
        if(x==1)
        {
            int x1,y1,x2,y2,w;
            cin>>x1>>y1>>x2>>y2>>w;
            add(x1,y1,x2,y2,w);
        }
        if(x==2)
        {
            int x,y;
            cin>>x>>y;
            cout<<sum(x,y)<<endl;
        }
    }
    return 0;
}

最後思考如何區間修改+區間查詢

類比之前一維陣列的區間修改區間查詢(這個部落格沒有),下面這個式子表示的是點(x, y)的二維字首和:

類比一維陣列,統計一下每個出現過多少次。出現了次,出現了次……出現了 次。

那麼這個式子就可以寫成:

把這個式子展開,就得到:

那麼我們要開四個樹狀陣列,分別維護:

,,,

這樣就可以解決上述問題了

程式碼如下:

void update(int x,int y,int z)
{
    for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=m;j+=lowbit(j))
        d1[i][j]+=z;
    	d2[i][j]+=z*x;
    	d3[i][j]+=z*y;
    	d4[i][j]+=z*x*y;
}
void add(int x1,int y1,int x2,int y2,int w)
{
    update(x1,y1,w);            
	update(x2+1,y2+1,w);
	update(x2+1,y1,-w);
	update(x1,y2+1,-w);
}
int sum(int x,int y)
{
    int ret=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
        ret+=(x+1)*(y+1)*d1[i][j]-(y+1)*d2[i][j]-(x+1)*d3[i][j]+d4[i][j];
    return ret;
}
int ask(int x1,int y1,int x2,int y2)
{
    return sum(x2,y2)-sum(x2,y1-1)-sum(x1-1,y2)+sum(x1-1,y1-1);
}

模板題

BZOJ 3132(自己上網搜吧)

程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>

using namespace std;
const int maxn=2105;
const int maxm=2105;
int n,m;
int a[maxn][maxm],d1[maxn][maxm],d2[maxn][maxm],d3[maxn][maxm],d4[maxn][maxm];

int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int y,int z)
{
    for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=m;j+=lowbit(j)){
        d1[i][j]+=z;
    	d2[i][j]+=z*x;
    	d3[i][j]+=z*y;
    	d4[i][j]+=z*x*y;
	}
}
void add(int x1,int y1,int x2,int y2,int w)
{
    update(x1,y1,w);            
	update(x2+1,y2+1,w);
	update(x2+1,y1,-w);
	update(x1,y2+1,-w);
}
int sum(int x,int y)
{
    int ret=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
        ret+=(x+1)*(y+1)*d1[i][j]-(y+1)*d2[i][j]-(x+1)*d3[i][j]+d4[i][j];
    return ret;
}
int ask(int x1,int y1,int x2,int y2)
{
    return sum(x2,y2)-sum(x2,y1-1)-sum(x1-1,y2)+sum(x1-1,y1-1);
}
int main()
{
	char op[2];
	scanf("%s",op);
    cin>>n>>m;
    while(scanf("%s",op)!=-1)
    {
        if(op[0]=='L')
        {
            int x1,y1,x2,y2,w;
            cin>>x1>>y1>>x2>>y2>>w;
            add(x1,y1,x2,y2,w);
        }
        else
        {
            int x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            cout<<ask(x1,y1,x2,y2)<<endl;
        }
    }
    return 0;
}

三維樹狀陣列

怎麼拓展呢?直接在二維樹狀陣列的基礎上加一維就可以了,不用進行任何改動,這裡我們只介紹其中的一種變式,那就是三維樹狀陣列修改區間查詢點

(如果有人出三維樹狀陣列修改區間查詢區間的那種題,直接在二維樹狀陣列修改區間查詢區間的基礎上改,應該不會有這種題的)

下面給出程式碼,著重觀察修改部分就可以了。

#include<iostream>
using namespace std;
const int maxn=105;
const int maxm=105;
const int maxl=105;
int n,m,l;
int q;
int a[maxn][maxm][maxl];
int c[maxn][maxm][maxl];
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int y,int z,int w)
{
    for(int i=x;i<=n;i+=lowbit(i))
    for(int j=y;j<=m;j+=lowbit(j))
    for(int k=z;k<=l;k+=lowbit(k))
        c[i][j][k]+=w;
}

int sum(int x,int y,int z)
{
    int ret=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
    for(int k=z;k>=1;k-=lowbit(k))
        ret+=c[i][j][k];
    return ret;
}
int main()
{
    cin>>n>>m>>l;
    cin>>q;
    while(q--)
    {
        int x;
        cin>>x;
        if(x==1)
        {
            int x1,y1,z1,x2,y2,z2,w;
            cin>>x1>>y1>>z1>>x2>>y2>>z2>>w;
             update(x1,y1,z1,w);
             update(x1,y2+1,z1,-w);
             update(x2+1,y1,z1,-w);
             update(x2+1,y2+1,z1,w);

             update(x1,y1,z2+1,-w);
             update(x1,y2+1,z2+1,w);
             update(x2+1,y1,z2+1,w);
             update(x2+1,y2+1,z2+1,-w);
        }
        if(x==2)
        {
            int x,y,z;
            cin>>x>>y>>z;
            cout<<sum(x,y,z)<<endl;
        }
    }
    return 0;
}

部分段落來自靜聽風吟。Lv1_kangdi