zoukankan      html  css  js  c++  java
  • 浅谈二维树状数组

    ①前置知识

    静态二维前缀和:

    ①:预处理递推: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].

    其实画一下图就很好理解了,具体详细教程从dalao的这篇blog: 传送门.

    ②二维树状数组

    I.(单点修改,区间查询)

    考虑一个点( X , Y )的存在,我们过这个点分别做X轴,Y轴的平行线,把这个点看做矩形的左上角,发现它只对它右下角的矩形才产生贡献.我们不由地联想到树状数组,用tree[ x ][ y ]的二维去维护点对'x下方', 'y右方'的贡献(想一想lowbit的作用).又因为树状数组求的和为前缀和,所以只要套静态二维前缀的的区间查询公式即可.

    以LOJ的板子为例:

    #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
    ",ans); 
            }
        }
        return 0;
    } 

    II.(区间修改,区间查询)

    ①:一维树状数组的区间修改区间查询.

    将单点修改,区间查询的差分技巧运用,容易发现 Σ(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
    ",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
    ",ans);
            }
        }
        return 0;
    }

    后记:考虑无修改操作时,n,m>=1e6,查询矩阵内元素个数,传送门.

    附上dalao blog.

  • 相关阅读:
    Docker系列
    Eclipse 安装TestNG插件,结合Maven使用
    HttpClient设置忽略SSL,实现HTTPS访问, 解决Certificates does not conform to algorithm constraints
    Jenkins Html Rport 使用frame报错解决办法
    Zend Framework1 框架入门(针对Windows,包含安装配置与数据库增删改查)
    Windows下Nginx配置SSL实现Https访问(包含证书生成)
    Windows下Nginx Virtual Host多站点配置详解
    幽灵般的存在:零宽空白
    我的公司培训讲义(2):设计模式思想精要教程
    突如其来而又必然的离职
  • 原文地址:https://www.cnblogs.com/xqysckt/p/11665073.html
Copyright © 2011-2022 走看看