淺談二維樹狀數組

①前置知識
html

靜態二維前綴和:
ios

①:預處理遞推:f[ i ][ j ] = f[ i - 1 ][ j ] + f[ i ][ j -1 ]  - f[ i - 1][ j - 1] + val[ i ][ j ].數組

②:左上角( X1 , Y),右下角( X, Y2),這一段的區間和:f[ X2 ][ Y2]  - f[ X2 ][ Y1 - 1] -f[ X1 - 1][ Y2 ] + f[ X1 - 1][Y1 - 1].spa

其實畫一下圖就很好理解了,具體詳細教程從dalao的這篇blog: 傳送門.code

②二維樹狀數組htm

I.(單點修改,區間查詢)
blog

考慮一個點( X , Y )的存在,咱們過這個點分別作X軸,Y軸的平行線,把這個點看作矩形的左上角,發現它只對它右下角的矩形才產生貢獻.咱們不禁地聯想到樹狀數組,用tree[ x ][ y ]的二維去維護點對'x下方', 'y右方'的貢獻(想想lowbit的做用).又由於樹狀數組求的和爲前綴和,因此只要套靜態二維前綴的的區間查詢公式便可.教程

以LOJ的板子爲例:get

 

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const int maxn = (1<<13);
LL n,m,flag,x1,y1,tree[maxn][maxn];
inline long long fd(){
    LL s=1,t=0;
    char  c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        t=t*10+c-'0';
        c=getchar();
    }
    return s*t;
}
LL lowbit(LL x){
    return x&(-x);
}
void add(LL x,LL y,LL v){
    for(re LL i=x;i<=n;i+=lowbit(i))
        for(re LL j=y;j<=m;j+=lowbit(j))
            tree[i][j] += v;
}
LL getans(LL x,LL y){
    LL sum = 0;
    for(re LL i=x;i;i-=lowbit(i))
        for(re LL j=y;j;j-=lowbit(j))
            sum += tree[i][j];
    return sum;
}
int main()
{ 
    n = fd(),m = fd();
    while((scanf("%lld%lld%lld",&flag,&x1,&y1)) != EOF){
        if(flag == 1){
            LL v = fd();
            add(x1,y1,v); 
        } 
        else if(flag == 2){
            LL x2 = fd(),y2 = fd();
            LL ans = getans(x2,y2)-getans(x2,y1-1)-getans(x1-1,y2)+getans(x1-1,y1-1);
            printf("%lld\n",ans); 
        }
    }
    return 0;
} 

 

II.(區間修改,區間查詢)string

①:一維樹狀數組的區間修改區間查詢.

將單點修改,區間查詢的差分技巧運用,容易發現 Σ(p , i =1)a[ i ] =Σ( p, i = 1)Σ(i , j = 1)cf[ j ].容易發現cf[ 1 ]用了p次,cf[ 2 ]用了p - 1次......那麼上式可化簡爲Σ(p,i = 1)cf[ i ]*(p-i+1),將公式展開變成(p + 1)*Σ(p , i = 1)cf[ i ]  - Σ(p , i =1)cf[ i ] * i.用樹狀數組去維護cf[ i ]與cf[ i ]*i的值便可.以LOJ例題爲例:

 

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const int maxn = 1e6+10;
long long n,q,a[maxn],tree1[maxn],tree2[maxn];
inline LL fd(){
    LL s = 1,t = 0;
    char c = getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
            s=-1;
        c = getchar();
    }
    while(c>='0'&&c<='9'){
        t = t*10+c-'0';
        c = getchar();
    }
    return s*t;
}
LL lowbit(LL x){
    return x&(-x);
}
void add(LL x,LL v){
    for(re LL i=x;i<=n;i+=lowbit(i))
        tree1[i] += v,tree2[i] += v*x;
}
LL ask(LL x){
    LL s = 0;
    for(re LL i=x;i;i-=lowbit(i))
        s += (x+1)*tree1[i] - tree2[i];
    return s;
}
int main()
{
    n = fd(),q = fd();
    for(re LL i=1;i<=n;++i)
        a[i] = fd();
    for(re LL i=1;i<=n;++i)
        add(i,a[i]-a[i-1]);
    while(q--){
        LL flag = fd();
        if(flag == 1){
            LL l = fd(),r = fd(),v = fd();
            add(l,v),add(r+1,-v);
        }
        else if(flag == 2){
            LL l = fd(),r = fd();
            printf("%lld\n",ask(r)-ask(l-1));
        }
    }
    return 0;
}

 

②:二維樹狀數組的區間修改與查詢.

①:咱們須要類比一維數組的區間修改與查詢,這時咱們要去定義一個二維的差分數組cf[ i ][ j ]表示val[ i ][ j ]與val[ i -1][ j ] + val[ i ][ j -1 ]  - val[ i -1][ j -1]的差.

那麼以一個點(x , y)爲右下角的矩陣內元素個數爲Σ(x , i = 1)Σ(y, j = 1)Σ(i,k = 1)Σ(j,t = 1)cf[ k ][ t ].

②:將上式展開,Σ(x , i = 1)Σ(y, j = 1)cf[ i ][ j ]*(x - i + 1)*(y - j + 1),再展開,(x + 1)( y + 1)*Σ(x , i = 1)Σ(y, j = 1)cf[ i ][ j ] - (y+1)Σ(x , i = 1)Σ(y, j = 1)cf[ i ][ j ]*i

- (x + 1)Σ(x , i = 1)Σ(y, j = 1)cf[ i ][ j ]*j + Σ(x , i = 1)Σ(y, j = 1)cf[ i ][ j ]*i*j.用樹狀數組去維護cf[ i ][ j ],cf[ i ][ j ]*i,cf[ i ][ j ]*j,cf[ i ][ j ]*i*j,四個信息便可.

③:同時注意,修改時有別於一維,(x1 , y1)爲左上角,(x2 , y2)爲右下角的矩陣加v時,(x1,y1) + v,(x1,y1 + 1) - v,(x2 + 1,y1)-v,(x2 + 1,y2 + 1) + v.

以LOJ例題爲例:

 

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define e exit(0)
#define re register
#define LL long long
const int maxn = 3010;
LL n,m,flag,t1[maxn][maxn],t2[maxn][maxn],t3[maxn][maxn],t4[maxn][maxn];
inline LL fd(){
    LL s=1,t=0;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-')
            s=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        t=t*10+c-'0';
        c=getchar();
    }
    return s*t;
}
LL lowbit(LL x){
    return x&(-x);
}
void add(LL x,LL y,LL v){
    for(re LL i=x;i<=n;i+=lowbit(i))
        for(re LL 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 rang_add(LL x1,LL y1,LL x2,LL y2,LL v){
    add(x1,y1,v);
    add(x2+1,y1,-v);
    add(x1,y2+1,-v);
    add(x2+1,y2+1,v);
}
LL ask(LL x,LL y){
    LL s = 0;
    for(re LL i=x;i;i-=lowbit(i))
        for(re LL j=y;j;j-=lowbit(j))
            s += (x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x+1)*t3[i][j]+t4[i][j];
    return s;
}
LL rang_ask(LL x1,LL y1,LL x2,LL y2){
    return ask(x2,y2)-ask(x2,y1-1)-ask(x1-1,y2)+ask(x1-1,y1-1);
}
int main()
{
    n = fd(),m = fd();
    while(scanf("%lld",&flag)!=EOF){
        if(flag == 1){
            LL x1 = fd(),y1 = fd(),x2 = fd(),y2 = fd(),v = fd();
            rang_add(x1,y1,x2,y2,v);
        }
        else if(flag == 2){
            LL x1 = fd(),y1 = fd(),x2 = fd(),y2 = fd();
            LL ans = rang_ask(x1,y1,x2,y2);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

 

後記:考慮無修改操做時,n,m>=1e6,查詢矩陣內元素個數,傳送門.

附上dalao blog.

相關文章
相關標籤/搜索