树状数组

Posted darlingroot

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组相关的知识,希望对你有一定的参考价值。

很久之前写过一篇

只不过很短(我太弱了)

倒还基础

传送门

 

1.单点查询+区间修改

 

#include<cstdio>
using namespace std;
int n,m,u,x,y,z;
int a[500005];

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

void update(int c,int d)
{
    while(c <= n)
    {
        a[c] += d;
        c += lowbit(c);
    }
}

int query(int c)
{
    int sum = 0;
    while(c > 0)
    {
        sum += a[c];
        c -= lowbit(c);
    }
    return sum;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d",&u);
        update(i,u);
    }
    for(int i = 1; i <= m; i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        if(x == 1)
            update(y,z);
        if(x == 2)
            printf("%d\\n",query(z) - query(y-1));
    }
    return 0;
}

 

2.区间修改+单点查询

  差分的思想

查询

设原数组为a[ i ],

设数组tree[ i ] = tree[ i ] - tree[i - 1](a[ 0 ] = 0)

则a[ i ] = tree[1] + tree[2] +...tree[ i ];

修改

当给区间[ l,r ]加上x的时候时

a[ l ]与前一个元素a[l - 1]的差增加了x

a[r + 1]与a[r]的差减少了x

根据tree[ i ]数组的定义

只需要给a[ l ]加上x

给a[r + 1]减去x即可

 

 

#include <cstdio>
#define lowbit(x) x & -x

using namespace std;

int tree[500005];
int n, m;

void add(int x,int num) 
{
    while (x <= n) 
    {
        tree[x] += num;
        x += lowbit(x);
    }
}

int query(int x) 
{
    int ans = 0;
    while (x) 
    {
        ans += tree[x];
        x -= lowbit(x);
    }
    return ans;
}

int main() 
{
    scanf("%d%d", &n, &m);
    int last = 0, now;
    for (int i = 1; i <= n; i++) 
    {
        scanf("%d", &now);
        add(i, now - last);
        last = now;
    }
    int flg;
    while (m--) 
    {
        scanf("%d", &flg);
        if (flg == 1) 
        {
            int x, y;
            int k;
            scanf("%d%d%d", &x, &y, &k);
            add(x, k);
            add(y + 1, -k);
        } else 
        if (flg == 2) 
        {
            int x;
            scanf("%d", &x);
            printf("%d\\n", query(x));
        }
    }
    return 0;
}

 

3.区间修改+区间查询

差分的思想

 

技术图片

查询

位置p的前缀和即:(p + 1)* sum1数组中p的前缀和 - sum2数组中p的前缀和

区间[ l , r]的和即:位置r的前缀和 - 位置l - 1的前缀和

修改

对于sum1数组的修改同2中d数组的修改

对于sum2数组的修改也类似,给sum2[ l ]加上l * x,给sum2[r + 1]减去(r + 1)* x

 

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;

inline ll read()
{
    ll sum = 0,p = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
        if(ch == 0)
            p = -1;
        ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
        (sum *= 10) += ch - 0;
        ch = getchar();
    }
    return sum * p;
}

const ll maxn = 1e5 + 5 , maxm = 1e5 + 5;
ll n,m;
ll v[maxn],sum1[maxn],sum2[maxn];

ll lowbit(ll o)
{
    return o&(-o);
}

void update(ll x,ll k)
{
    int i = x;
    while(x <= n)
    {
        sum1[x] += k;
        sum2[x] += i * k;
        x += lowbit(x);
    }
}

ll query(ll o)
{
    ll ans = 0;
    for(ll i = o;i;i -= lowbit(i))
        ans += (o + 1) * sum1[i] - sum2[i];
    return ans;
}

int main()
{
    n = read(),m = read();
    for(ll i = 1;i <= n;i++)
    {
        v[i] = read();
        update(i,v[i] - v[i - 1]);
    }
    for(ll i = 1;i <= m;i++)
    {
        ll opt = read(),x,y,k;
        if(opt == 1)
        {
            x = read(),y = read(),k = read();
            update(x,k);
            update(y + 1,-k);
        }
        if(opt == 2)
        {
            x = read(),y = read();
            printf("%lld\\n",query(y) - query(x - 1));
        }
    }
    return 0;
}

 

 

二维树状数组

 

运用类比的思想(呕自己

一维树状数组中tree[x]中记录的是右端点为x、长度为lowbit(x)的区间的区间和

二维树状数组中,定义tree[x][y],记录的是右下角为(x,y),高为lowbit(x),宽为lowbit(y)的区间的区间和

 

一、单点修改+区间查询

 

//单点修改+区间查询
void update(int x,int y,int z)//将点(x,y)加上z 
{
    int mrk = y;
    while(x <= n)
    {
        y = mrk;
        while(y <= n)
        {
            tree[x][y] += z;
            y += lowbit(y);
        }
        x += lowbit(x);
    }
}
void query(int x,int y)//查询左上角为(1,1),右下角为(x,y)的矩阵和 
{
    int ans = 0,mrk = y;
    while(x)
    {
        y = mrk;
        while(y)
        {
            ans += tree[x][y];
            y -= lowbit(y);
        }
        x -= lowbit(x);
    }
}

 

 

二、区间修改+单点查询

“差分”思想

由二维前缀和:

sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a[i][j]

可得

差分数组d[i][j] 为 a[i][j] 与 a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1]的差

举个例子

下面这个二位数组

1   4   8
6   7   2
3   9   5

对应的差分数组为

 1  3  4
 5 -2 -9
-3  5  1

给最中间的3*3矩阵加上x时

差分数组的变化:

0  0  0  0  0
0 +x  0  0 -x
0  0  0  0  0
0  0  0  0  0
0 -x  0  0 +x

这样修改差分,效果为:

0  0  0  0  0
0  x  x  x  0
0  x  x  x  0
0  x  x  x  0
0  0  0  0  0

 

//二维树状数组:区间修改+单点查询
void update(int x,int y,int z)
{
    int mrk = y;
    while(x <= n)
    {
        y = mrk;
        while(y <= n)
        {
            tree[x][y] += z;
            y += lowbit(y);
        }
        x += lowbit(x); 
    }
}

void range_update(int xa,int ya,int xb,int yb,int z)
{
    update(xa,ya,z);
    update(xa,yb+1,-z);
    update(xb+1,ya,-z);
    update(xb+1,yb+1,z);
}

void query(int x,int y)
{
    int mrk = y,ans = 0;
    while(x)
    {
        y = mrk;
        while(y)
        {
            ans += tree[x][y];
            y -= lowbit(y);
        }
        x -= lowbit(x); 
    }
}

 

三、区间修改+区间查询

技术图片

//二维树状数组:区间修改+区间查询
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;

inline ll read()
{
    ll sum = 0,p = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
        if(ch == -)
            p = -1;
        ch = getchar();
    }
    while(ch >= 0 && ch <= 9)
    {
        (sum *= 10) += ch - 0;
        ch = getchar();
    }
    return sum * p;
}

const int N =  205;
ll n,m,Q;
ll t1[N][N],t2[N][N],t3[N][N],t4[N][N];

ll lowbit(ll o)
{
    return o & -o;
}

void update(ll x,ll y,ll z)
{
    for(int X = x;X <= n;X += lowbit(X))
        for(int Y = y;Y <= m;Y += lowbit(Y))
        {
            t1[X][Y] += z;
            t2[X][Y] += z * x;
            t3[X][Y] += z * y;
            t4[X][Y] += z * x * y;
        }
}

void range_update(ll xa,ll ya,ll xb,ll yb,ll z)
{
    update(xa,ya,z);
    update(xa,yb + 1,-z);
    update(xb + 1,ya,-z);
    update(xb + 1,yb + 1,z);
}

ll query(ll x,ll y)
{
    ll ans = 0;
    for(int i = x;i;i -= lowbit(i))
        for(int j = y;j;j -= lowbit(j))
            ans += (x + 1) * (y + 1) * t1[i][j] - (y + 1) * t2[i][j] - (x + 1) * t3[i][j] +t4[i][j];
    return ans;
}

ll range_query(ll xa,ll ya,ll xb,ll yb)
{
    return query(xb, yb) - query(xb, ya - 1) - query(xa - 1, yb) + query(xa - 1, ya - 1);
}

int main()
{
    n = read(),m = read(),Q = read();
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
        {
            ll z = read();
            range_update(i,j,i,j,z);
        }
    while(Q--)
    {
        ll ya = read(),xa = read(),yb = read(),xb = read(),z = read(),a = read();
        if(range_query(xa,ya,xb,yb) < z * (xb - xa + 1) * (yb - ya + 1))
            range_update(xa,ya,xb,yb,a);
    }
    for(int i = 1;i <= n;i++)
    {
        for(int j = 1;j <= m;j++)
            printf("%lld",range_query(i,j,i,j));
        printf("\\n");
    }
    return 0;
} 

 

以上是关于树状数组的主要内容,如果未能解决你的问题,请参考以下文章

数据结构之树状数组从零认识树状数组

树状数组和线段树有啥区别?

树状数组

树状数组

树状数组

如何利用树状数组修改一个区间?