「学习笔记」一维树状数组

树状数组是一种查询和修改都是 Ologn 的数据结构
树状数组可以做到单点修改区间查询,单点查询区间修改和区间修改区间查询
树状数组也分一维和二维

由此可以看出
C1=A1
C2=A1+A2
C3=A3
C4=A1+A2+A3+A4
C5=A5
C6=A5+A6
C7=A7
C8=A1+A2+A3+A4+A5+A6+A7+A8
......

要时刻记着每个点是怎么来的,否则后面容易混!!!

设节点编号为 n,那么,它所管辖的区域就是 2jjn 的二进制形式的末尾 0 的个数) 子节点找父节点就是加上最后一个 1

e.g.
1>2—— 0001+0001=0010
2>4—— 0010+0010=0100
3>4—— 0011+0001=0100
那现在问题来了,怎么求最后一个 1
通常,我们把它叫做 lowbit(n)
计算方法 n & (-n)
&: 位运算符,按位与,只有参与运算的两个二进制数所对应的二进制位均为 1 ,结果对应位才为 1 ,否则为 0

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

单点修改,区间查询:#

问题:在 x 位置加上 y,求 ab的区间和。
如果 x 位置加上 y,那么,它的父节点也要加上 y,查询父节点位置前面已经提到了,这个操作叫单点修改。
单点修改代码:

void add(ll x,ll y)//数组x位置加y 
{
	for(rll i=x;i<=n;i+=(i&(-i)))
	{
		s[i]+=y;//逐层向上修改(毕竟子节点改了,父节点也要改) 
	}
}

建树的过程,就可以看作是单点修改。
区间查询,就是找类兄弟
e.g. (sum 可以看作前缀和)
sum5=S5+S4+0
S5:0101
S4:0100
0:0000
sum7=S7+S6+S4+0
S7:0111
S6:0110
S4:0100
0:0000
发现了吗,找类兄弟就是减去最后一个 1 ,直至没有 1 为止
区间查询代码:

ll sum(ll x)
{
    ll ans=0;
    for(rll i=x;i;i-=(lowbit(i)))
    {
        ans+=s[i];
    }
    return ans;
}

单点修改,区间查询完整代码(有注释):

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m,a;
ll s[1000100];
void add(ll x,ll y)//数组x位置加y
{
    for(rll i=x;i<=n;i+=(i&(-i)))
    {
        s[i]+=y;//逐层向上修改(毕竟子节点改了,父节点也要改)
    }
}
ll sum(ll x)
{
    ll ans=0;
    for(rll i=x;i;i-=(i&(-i)))
    {
        ans+=s[i];//求和,找类兄弟
    }
    return ans;
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for(rll i=1;i<=n;++i)
    {
        scanf("%lld",&a);
        add(i,a);//建树过程,把操作之前的树上的点都看作是0
    }
    for(rll p,l,r,i=1;i<=m;++i)
    {
        scanf("%lld%lld%lld",&p,&l,&r);
        if(p==1)
        {
            add(l,r);//在l位置加上r
        }
        else
        {
            printf("%lld\n",sum(r)-sum(l-1));//查询区间[l-r]
        }
    }
    return 0;
}

区间修改,单点查询:#

区间修改主要运用差分思想
注意,后面的内容必须会差分才可以看懂,不会差分的请止步。
区间修改代码(差分):

void add(ll x,ll y)
{
    for(rll i=x;i<=n;i+=(i&(-i)))
    {
        s[i]+=y;
    }
}

单点查询,运用差分求和的思想,[1i] 的差分和就是第i个元素的数值
单点修改代码:

ll sum(ll x)
{
    ll ans=0;
    for(rll i=x;i;i-=(i&(-i)))
    {
        ans+=s[i];//利用差分的思想求和,1-i的差分数组的和就是第i个数的值
    }
    return ans;
}

区间修改,单点查询完整代码(有注释):

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m,last,a;
ll s[1000100];
void add(ll x,ll y)//差分数组x的位置加y
{
    for(rll i=x;i<=n;i+=(i&(-i)))
    {
        s[i]+=y;
    }
}
ll sum(ll x)//求第x个数的数值
{
    ll ans=0;
    for(rll i=x;i;i-=(i&(-i)))
    {
        ans+=s[i];//利用差分的思想求和,1-i的差分数组的和就是第i个数的值
    }
    return ans;
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for(rll i=1;i<=n;++i)
    {
        scanf("%lld",&a);
        add(i,a-last);//记录差分数组
        last=a;//更新last,记录差分
    }
    for(rll p,l,r,j,i=1;i<=m;++i)
    {
        scanf("%lld",&p);
        if(p==1)
        {
            scanf("%lld%lld%lld",&l,&r,&j);//在区间[l-r]中,每一个元素加j
            add(l,j);//差分数组s[l]+j
            add(r+1,-j);//差分数组s[r+1]-j
        }
        else
        {
            scanf("%lld",&j);
            printf("%lld\n",sum(j));//求点j的值,利用差分求和
        }
    }
    return 0;
}

区间修改,区间查询:#

这里的区间修改依然是差分思想,但求的是区间和,因此,与上面的有不同

区间和就是这个区间的数都加一遍, d(j) 是差分数组,差分数组从 1i 的和就是 a(i)

这里要自己亲自加一遍试试,全加起来,就会发现,d(1),d(2),d(3)... 被加的个数有规律,最后用分配律,就是最终公式了。

公式一定要记住!!!

由此可见,区间修改,区间查询需要多维护一个 d(i)×i 的和值

区间修改代码:

void add(ll x,ll y)//差分数组x位置的数加y
{
    for(rll i=x;i<=n;i+=(i&(-i)))
    {
        s1[i]+=y;//记录差分
        s2[i]+=x*y;//记录差分的和
    }
}

区间查询代码:

ll sum(ll x)//求前缀和(准确来说也不是前缀和,但性质差不多)
{
    ll ans=0;
    for(rll i=x;i;i-=(i&(-i)))
    {
        ans+=(x+1)*s1[i]-s2[i];//根据公式计算
    }
    return ans;
}

scanf("%lld%lld",&l,&r);//求区间(l-r)的和
printf("%lld\n",sum(r)-sum(l-1));//前缀和计算

区间修改,区间查询完整代码(有注释):

#include<bits/stdc++.h>
#define ll long long
#define rll register long long
using namespace std;
ll n,m,a,last;
ll s1[1000100],s2[1000100];
// s1 记录差分树状数组(公式第一项) s2 记录差分数组×i的和 (公式第二项)
void add(ll x,ll y)//差分数组x位置的数加y
{
    for(rll i=x;i<=n;i+=(i&(-i)))
    {
        s1[i]+=y;//记录差分
        s2[i]+=x*y;//记录差分的和
    }
}
ll sum(ll x)//求前缀和(准确来说也不是前缀和,但性质差不多)
{
    ll ans=0;
    for(rll i=x;i;i-=(i&(-i)))
    {
        ans+=(x+1)*s1[i]-s2[i];//根据公式计算
    }
    return ans;
}
int main()
{
    scanf("%lld%lld",&n,&m);
    for(rll i=1;i<=n;++i)
    {
        scanf("%lld",&a);
        add(i,a-last);//a-last 计算差分
        last=a;//更新last,使得记录的都是后一个数和前一个数的差
    }
    for(rll p,l,r,j,i=1;i<=m;++i)
    {
        scanf("%lld",&p);//判断是修改还是查询
        if(p==1)
        {
            scanf("%lld%lld%lld",&l,&r,&j);//l-r区间的元素加j
            add(l,j);//差分数组s1[l]+j
            add(r+1,-j);//差分数组s1[r+1]-j
        }
        else
        {
            scanf("%lld%lld",&l,&r);//求区间(l-r)的和
            printf("%lld\n",sum(r)-sum(l-1));//前缀和计算
        }
    }
    return 0;
}

喜欢的话,还请支持一下吧,写的不好,dalao 勿喷QWQ

作者:yifan0305

出处:https://www.cnblogs.com/yifan0305/p/16417679.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载时还请标明出处哟!

posted @   yi_fan0305  阅读(74)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示