树状数组几种板子题解析


首先我们应该知道树状数组的基本结构:
1.lowbit函数:此处不做解释( 视频讲的更好
2.add函数:修改(单点||区间)
3.getsum函数:查询(单点||区间)

往下看的前提是看过上述视频并了解了lowbit函数

A.单点修改,区间查询

洛谷
A. 单点修改,区间查询 [ 讨论 ]
Description
给定数列a[1],a[2],…,a[n],你需要依次进行 q个操作,操作有两类:

1 i x:给定i,x,将a[i]加上x;

2 l r:给定l,r,求 ∑ri=la[i]的值(换言之,求a[l]+a[l+1]+⋯+a[r]的值)

Input
第一行包含2个正整数n,q,表示数列长度个数,保证a≤n,q≤106
第二行n个整数a[1],a[2],…,a[n],表示初始数列,保证|a[i]|≤106
接下来q行,每行一个操作,为下列两种之一:

1 i x:给定i,x,将a[i]加上x;

2 l r:给定l,r,求 ∑ri=1a[i]的值;

保证1≤l≤r≤n, |x|≤106
Output
对于每个 2 l r 操作输出一行,每行有一个整数,表示所求的结果。

Samples
Input 复制
3 2
1 2 3
1 2 0
2 1 3
Output
6
Hint
对于所有数据,1≤n,q≤106,|a[i]|≤106,1≤l≤r≤n,|x|≤106


首先从单点修改开始:
假如初始数组为a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;
在这里插入图片描述
那么我们构建树状数组tree;
是不是 插入 1 有
tree[1]+=1, tree[1]=1;
tree[2]+=1,tree[2]=1;
tree[4]+=1,tree[4]=1;
插入2 有
tree[2]+=2, tree[2]=3;
tree[4]+=2,tree[4]=3;
插入3 有
tree[3]+=3,tree[3]=3;
tree[4]+=3,tree[4]=6;
插入4 有
tree[4]+=4,tree[4]=10;
插入5 有
tree[5]+=5,tree[5]=5;
那么我们此时来看 初始数列为 1 2 3 4 5;
前1项和 为1;
前2项和 为3;
前3项和 为6;
前4项和 为10;
前5项和 为15;
是不是发现前n项和的数值 是树状数组getsum(n)的数值?
比如5 getsum(5){
ans+=tree[5];
5-=lowbit(5)
}
ans=5;

getsum(4){
ans+=tree[4];
4-=lowbit(4)
}
ans=5+10=15;
4-lowbit(4)=0;
getsum(0) 错误退出返回ans;
getsum(5)=15;
所以此时你应该明白 getsum(n)求的是初始数列a[]的前n项和;

至于单点修改 add函数 :
初始数组为a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5;
如果我给数组2 加上3 a[2]=5;
数组为a[1]=1,a[2]=5,a[3]=3,a[4]=4,a[5]=5;
那么我是不是tree[2]也要改变 tree[2]+=3,tree[2]=6;
既然我getsum函数求的是前n项和,是不是跟tree[2]数组有关的tree数组我也要改变 2+=lowbit(2)=4;
在这里插入图片描述
这不直接是一条龙服务吗
tree[4]+=3,tree[4]=10+3=13;

总结:
add(n)函数是修改前n项的和||单点修改;
getsum(n)是求前n的和;

此处前n项和均不是树状数组前n项和,而是初始数组! 模拟一下getsum的过程你就会发现了;

那么我求区间【l,r】的和 是不是 getsum®-getsum(l-1);

#include<bits/stdc++.h>
using namespace std;
#define PI 3.1415926535897932384
typedef long long ll;
const int UNINF=-0x3f3f3f3f;
const int INF=0x3f3f3f3f;
const int maxn=1e6+7;
const int mod=1000;
inline ll read()
{
    ll x=0,zf=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            zf=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return zf*x;
}
#define read read()
#define size size()
//priority_queue<int,vector<int>,greater<int> >v;
//map<char,int>s;
//string s[maxn];
ll tree[maxn];
ll n,q;
ll lowbit(ll x){
    return x&-x;
}
void add(ll x,ll k){
    while(x<=n){
        tree[x]+=k;
        x+=lowbit(x);
        //cout<<x<<" "<<lowbit(x)<<endl;
    }
    return ;
}
ll getsum(ll x){
    ll ans=0;
    while(x){
        ans+=tree[x];
        x-=lowbit(x);
    }
    return ans;
}
int main()
{
 	int t;
    //cin>>n>>q;
	scanf("%lld %lld",&n,&q);
    memset(tree,0,sizeof(tree));
    for(int i=1;i<=n;i++) {
    	ll k;
		scanf("%lld",&k);
		add(i,k);
	}
    for(int i=1;i<=q;i++){
        ll id,x,y;
        //cin>>id>>x>>y;
        scanf("%lld %lld %lld",&id,&x,&y);
        if(id==1){
            add(x,y);
            //cout<<tree[x]<<endl;
        }
        if(id==2){
        	printf("%lld\n",getsum(y)-getsum(x-1));
            //cout<<getsum(y)-getsum(x-1)<<endl;
        }
    }


    return 0;
}

B.区间修改,单点查询

洛谷
B. 区间修改,单点查询 [ 讨论 ]
Description
给定数列a[1],a[2],…,a[n] ,你需要依次进行 q 个操作,操作有两类:

1 l r x:给定 l,r,x,对于所有i∈[l,r],将 a[i] 加上 x(换言之,将 a[l],a[l+1],…,a[r] 分别加上 x);
2 i:给定 i ,求 a[i] 的值。
Input
第一行包含 2 个正整数 n,q,表示数列长度和询问个数。保证1≤n,q≤106 。
第二行 n 个整数 a[1],a[2],…,a[n],表示初始数列。保证|a[i]|≤106 。
接下来 q 行,每行一个操作,为以下两种之一:

1 l r x:对于所有 i∈[l,r],将a[i]加上x;
2 i:给定 i,求 a[i] 的值。
保证 1≤l≤r≤106, |x|≤106。

Output
对于每个 2 i 操作,输出一行,每行有一个整数,表示所求的结果。

Samples
Input 复制
3 2
1 2 3
1 1 3 0
2 2
Output
2
Hint
对于所有数据,1≤n,q≤106 , |a[i]|≤106, 1≤l≤r≤n, |x|≤106

至于区间修改,单点查询:
我们要引入差分数组c[i]=a[i]-a[i-1];;
比如
a[0]=0;
c[1]=a[1]-a[0];
c[2]=a[2]-a[1];
那么我a[2]=c[2]+c[1]=a[2]-a[1]+a[1]-a[0]=a[2];
所以你会发现 我们求c数组(getsum(n)) 就是求的要查询的第n项的值;
所以我们就用tree储存差分值;
计算getsum(n) 不就是查询第n项的值了吗,所以和A题很相似,对比着去学;
至于区间修改:
比如对[l,r]区间进行修改加上k:
是不是我要对初始数组a在区间[l,r]内的全部元素加上k;
初始数组:5 4 2 3 1
差分 5 -1 -2 1 -2
初始差分数组:0 0 0 0 0
插入第1个差分 5:5 5 0 5 0
插入第2个差分 -1:5 5+(-1) 0 5+(-1) 0 →5 4 0 4 0
插入第3个差分 -2:5 4 0+(-2) 4+(-2) 0 →5 4 -2 2 0
插入第4个差分 1:5 4 -2 2+(1) 0→ 5 4 -2 3 0
插入第5个差分 -2:5 4 -2 3 0+(-2)→ 5 4 -2 3 -2

那么我们再用getsum(n) 是不是就是询问到第n项的值了
加上k=3之后 5 7 5 6 1
差分 5 2 -2 1 -5
聪明的你是不是发现 差分只有 开始的一项+k,还有区间外接下来的一项-k;(其实这个一想也知道,我把第l项的差分加上k,那我后面用差分求和的时候也是加了这一个k呀,为了不影响区间外的求和,我们要把区间外的紧挨着的一项-k呀)
所以就有
add(l,k)
add(r+1,-k);

#include<bits/stdc++.h>
using namespace std;
#define PI 3.1415926535897932384
typedef long long ll;
const int UNINF=-0x3f3f3f3f;
const int INF=0x3f3f3f3f;
const int maxn=1e6+7;
const int mod=1000;
inline ll read()
{
    ll x=0,zf=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            zf=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return zf*x;
}
#define read read()
#define size size()
//priority_queue<int,vector<int>,greater<int> >v;
//map<char,int>s;
//string s[maxn];
ll tree[1000006];
ll n,m,q;
ll lowbit(ll x){
    return x&-x;
}
void add(ll x,ll k){
    //cout<<n<<endl;
    while(x<=n){
        tree[x]+=k;
        x+=lowbit(x);
    }
    return ;
}
ll getsum(ll x){
    ll ans=0;
    while(x){
        ans+=tree[x];
        x-=lowbit(x);
    }
    return ans;
}

int main()
{   
    //ios::sync_with_stdio(false);
    memset(tree,0,sizeof(tree));
    scanf("%lld %lld",&n,&m);
    ll temp=0;
    ll kk;
    for(int i=1;i<=n;i++){
        scanf("%lld",&kk);
        add(i,kk-temp);
        temp=kk;
    }
    while(m--){
        ll id,x,y,k;
        scanf("%lld",&id);
        if(id==1){
        	scanf("%lld %lld %lld",&x,&y,&k);
            add(x,k);
            add(y+1,-k);
        }
        if(id==2){
            scanf("%lld",&x);
            printf("%lld\n",getsum(x));
            //cout<<getsum(x)<<endl;
        }
    }

    return 0;
}

C.区间修改,区间查询

C. 区间修改,区间查询 [ 讨论 ]
Description
给定数列 a[1],a[2],…,a[n],你需要依次进行q个操作,操作有两类:

1 l r x:给定l,r,x,对于所有的i∈[l,r],将a[i]加上x(换言之,将a[l],a[l+1],…,a[r] 分别加上x)
2 l r:给定l,r,求∑ri=la[i]的值(换言之,求a[l]+a[l+1]+…+a[r]的值)
Input
第一行包含2个正整数n,q,表示数列长度和询问个数。保证1≤n,q≤106;

第二行n个整数a[1],a[2],…,a[n],表示初始数列。保证|a[i]|≤106。

接下来q行,每行一个操作,为以下两种之一:

1 l r x:对于所有的i∈[l,r],将a[i]加上x;

2 l r:输出∑ri=la[i]的值;

Output
对于每个 2 l r 操作,输出一行,每行有一个整数,表示所求的结果。

Samples
Input 复制
5 10
2 6 6 1 1
2 1 4
1 2 5 10
2 1 3
2 2 3
1 2 2 8
1 2 3 7
1 4 4 10
2 1 2
1 4 5 6
2 3 4
Output
15
34
32
33
50
Hint
对于所有数据,1≤n,q≤106,|a[i]|≤106 ,1≤l≤r≤n,|x[i]|≤106 。
在这里插入图片描述
ok,把这个图片搞懂&&AB题能理解了 这个你就会做了 (借鉴大佬
累了不想写了

#include<bits/stdc++.h>
using namespace std;
#define PI 3.1415926535897932384
typedef long long ll;
const int UNINF=-0x3f3f3f3f;
const int INF=0x3f3f3f3f;
const int maxn=1e6+7;
const int mod=1000;
inline ll read()
{
    ll x=0,zf=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')
            zf=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return zf*x;
}
#define read read()
#define size size()
ll n,q;
ll c[maxn];
ll c1[maxn];
ll lowbit(ll x){
    return x&-x;
}
void addc(ll x,ll k){
    while(x<=n){
        c[x]+=k;
        x+=lowbit(x);
    }
    return ;
}
ll askc(ll x){
    ll ans=0;
    while(x){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
void addc1(ll x,ll k){
    while(x<=n){
        c1[x]+=k;
        x+=lowbit(x);
    }
    return ;
}
ll askc1(ll x){
    ll ans=0;
    while(x){
        ans+=c1[x];
        x-=lowbit(x);
    }
    return ans;
}
int main()
{
    scanf("%lld %lld",&n,&q);
    int temp=0;
    for(int i=1;i<=n;i++){
        ll x;
        scanf("%lld",&x);
        addc(i,x-temp);
        addc1(i,(i-1)*(x-temp));//我跟大佬代码的区别也就在这了
        temp=x;
    }
    while(q--){
        ll id,l,r,xx;
        scanf("%lld",&id);
        if(id==1){
            scanf("%lld %lld %lld",&l,&r,&xx);
            addc(l,xx);
            addc(r+1,-xx);
            addc1(l,(l-1)*xx);
            addc1(r+1,-(r+1-1)*xx);
        }
        if(id==2){
            scanf("%lld %lld",&l,&r);
            ll ans;
            ans=(r*askc(r)-askc1(r))-((l-1)*askc(l-1)-askc1(l-1));
            printf("%lld\n",ans);
        }
    }



    return 0;
}

本人小白,避免不了出现错误,还请各位大牛指正!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值