树状数组(一二维)//洛谷P3374+洛谷P3368+Gym237040E

第一行——咕咕咕。

第二行——本来没想写树状数组的,因为在四月就学会了而且做了一些题,觉得用处不大。直到——我又遇到树状数组的题而且我不会做了我哭辽,我对树状数组一点印象都没了,就三个月没做树状数组的题啊!!!!!于是愉快补学+补博客,以免将来再忘记+不好找板子。

第三行——查询和修改都是log(n)

 

以下为正文:

一、一维树状数组

1.树状数组:对于数组A1~An,我们用C1 = A1,C2 = A1+A2,C3 = A3,C4 = A1+A2+A3+A4……依次类推。凡是下标末尾为0的(二进制),有k个零即为前2^k个数的和,下标为2的幂的为前面所有数的和。lowbit返回的是最右边的第一个1的值(二进制)。C即为一个树状数组。

因为我们是按二进制末位是否为1储存,所以在处理时需要找到哪个C存了相应的A来进行修改,要用到lowbit函数。其他详细解释都在代码里。

2.多用于:①.单点修改,区间查询(区间和之类的)②.区间修改,单点查询

3.其他:维护和查询的时间复杂度都为logN。据说能用树状数组做的都能用线段树做,用线段树能做的树状数组不一定能做。但我不会线段树。

练习题目:

1.洛谷P3374(单点修改区间查询)(模板)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long

using namespace std;

ll c[1000005] = {0};//初始化为0

int lowbit(int x)
{
    return (x)&(-x);
}//该操作可以得到2^k次方的值。
//只保留最低位的1(二进制下),即从左往右返回第一个1(ex:1101——>1)

void update(int x,int y,ll z)
{
    while(x <= y)
    {
        c[x] += z;
        x += lowbit(x);//往后跳的位数(哪一位有需要更改的元素)
    }
}//x表示点,y表示共有几个点,z表示要更改的值

ll sum(int r)
{
    ll tot = 0;
    while(r > 0)
    {
        tot += c[r];
        r -= lowbit(r);//往前跳的位数
    }
    return tot;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; ++i)
    {
        ll a;
        scanf("%lld",&a);
        update(i,n,a);
    }
    for(int i = 0; i < m; ++i)
    {
        int op;
        scanf("%d",&op);
        if(op == 1)
        {
            int x;
            ll y;
            scanf("%d%lld",&x,&y);
            update(x,n,y);
        }
        else if(op == 2)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            ll res = sum(y) - sum(x-1);//求区间值
            printf("%lld\n",res);
        }
    }
    return 0;
}

2.洛谷P3368(区间修改单点查询)(模板)

上面就单纯正向思维,这道题运用了差分的思想。令B[1] = A[1],B[2] = A[2]-A[1],B[3] = A[3]-A[2]……我们可以看出,A[x] = B[1]+B[2]+……+B[x]。所以,我们可以用B来进行A中的区间修改,但只改B中两个点的值,C进行维护B的和,即C为树状数组。

其实就是相当于多了个预处理A为B。

哎不对啊,用不着B我为什么要写一个函数维护B的值??我是个傻的

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long

using namespace std;

ll b[500005] = {0};
ll a[500005] = {0};
ll c[500005] = {0};
int n;

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

void update(int x,int y,ll z)
{
    while(x <= y)
    {
        c[x] += z;
        x += lowbit(x);
    }
}

void add(int x,int y,ll z)
{
    b[x] += z;
    b[y+1] -= z;
}

ll chazhao(int x)
{
    ll res = 0;
    while(x > 0)
    {
        res += c[x];
        x -= lowbit(x);
    }
    return res;
}

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; ++i)
    {
        scanf("%lld",&a[i]);
        b[i] = a[i] - a[i-1];
        update(i,n,b[i]);
    }
    for(int i = 0; i < m; ++i)
    {
        int op;
        scanf("%d",&op);
        if(op == 1)
        {
            int x;
            int y;
            ll z;
            scanf("%d%d%lld",&x,&y,&z);
            add(x,y,z);
            update(x,n,z);
            update(y+1,n,-z);
        }
        else if(op == 2)
        {
            int x;
            scanf("%d",&x);
            ll res = chazhao(x);
            printf("%lld\n",res);
        }
    }
    return 0;
}

 

二、二维树状数组

类似一维数组,只不过从求一段变成了求一个矩形区域。也同样是单点更新区间查询+区间更新单点查询。

题目:一个由数字构成的矩阵,对矩阵里的某个数加上一个整数,查询某个子矩阵里所有数字的和,对每次查询,输出结果。

用树状数组C[x][y]记录右下角为x,y,高为lowbit(x), 宽为 lowbit(y)的矩阵和(就跟一维同理,为2的幂就全部)

好了下午集训时间到了下班了回家喽留个坑,晚上写。好了六点半了我回来了。

1.Gym237040E(单点更新区间查询)(模板)

思路:就跟一维的一样,注意减重了再加上就行。最后求和ans那里不一定要这样加减,只要把重复减掉的加上就可以了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define ll long long

using namespace std;

ll c[4100][4100] = {0};
int n,m;

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

void update(int x,int y,int k)
{
    for(int i = x; i <= n; i += lowbit(i))
        for(int j = y; j <= m; j += lowbit(j))
            c[i][j] += k;
}

ll add(int p,int q)
{
    ll res = 0;
    while(p)
    {
        int z = q;
        while(z)
            res += c[p][z],z -= lowbit(z);
        p -= lowbit(p);
    }
    return res;
}
int main()
{
    int op;
    scanf("%d%d",&n,&m);
    memset(c,0,sizeof(c));
    while(~scanf("%d",&op))
    {
        if(op == 1)
        {
            int x,y,k;
            scanf("%d%d%d",&x,&y,&k);
            update(x,y,k);
        }
        else if(op == 2)
        {
            int z1,z2,r1,r2;
            scanf("%lld%lld%lld%lld",&z1,&z2,&r1,&r2);
            ll ans = add(r1,r2) - add(r1,z2-1) - add(z1-1,r2) + add(z1-1,z2-1);
            printf("%lld\n",ans);
        }
    }
    return 0;
}

2.(区间更新单点查询)这个我还没遇到题,等遇到了再放上来。

 

 

 

好了接下来就是线段树了qwq

欢迎指出错误qwq

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值