“高级”数据结构——树状数组

从该博客学习后写下的这篇博客...

线段树(如果不明白线段树请自行百度)

在线段树的基础上只留下左子树,然后进行一些列操作就是树状数组

 

1. 单点修改 + 区间查询

函数模板​:

int bit[maxn],n,m; //n:总的区间长度  m:m次操作 

void update(int i,int x){ //给位置i增加x 
	while(i<=n){
		bit[i]+=x;
		i+=i&-i;   //i&-i得到i的二进制最后一个1 
	}
}

int sum(int i){  //求位置i的前缀和 
	int s=0;
	while(i>0){
		s+=bit[i];
		i-=i&-i;
	}
	return s;
}

int range_sum(int l,int r){ //区间求和 
	return sum(r)-sum(l-1);
} 

​

 如下是代码模板,只需要根据题意修改‘...’的内容即可

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
const int INF = 0x3f3f3f3f;

int bit[maxn],n,m; //n:总的区间长度  m:m次操作 

void update(int i,int x){ //给位置i增加x 
    while(i<=n){
        bit[i]+=x;
        i+=i&-i;   //i&-i得到i的二进制最后一个1 
    }
}

int sum(int i){  //求位置i的前缀和 
    int s=0;
    while(i>0){
        s+=bit[i];
        i-=i&-i;
    }
    return s;
}

int range_sum(int l,int r){ //区间求和 
    return sum(r)-sum(l-1);

int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&bit[i]);
        update(i,bit[i]);
    }
    while(m--){
        ...;
    }
    return 0;
}

 2. 区间修改 + 单点查询

通过“差分”(就是记录数组中每个元素与前一个元素的差),可以把这个问题转化为问题1。

查询

设原数组为a[i], 设数组bit[i]=a[i]-a[i-1](a[0]=0),则 a[i]=bit[1]+...+bit[i],可以通过求bit[i]的前缀和查询。

修改

当给区间[l,r]加上x的时候,a[l]与前一个元素 a[l-1]的差增加了x,a[r+1]与a[r]的差减少了x。
根据d[i]数组的定义,只需给a[l]加上x,给a[r+1]减去x即可。

函数模板

int a[maxn],bit[maxn],n,m; //n:总的区间长度  m:m次操作 

void add(int i,int x){ //给位置i增加x 
	while(i<=n){
		bit[i]+=x;
		i+=i&-i;   //i&-i得到i的二进制最后一个1 
	}
}

void range_add(int l,int r,int x){//给区间[l, r]加上x
	add(l,x),add(r+1,-x);
}

int ask(int i){  //单点查询
	int res=0;
	while(i>0){
		res+=bit[i];
		i-=i&-i;
	}
	return res;
}

  如下是代码模板,只需要根据题意修改‘...’的内容即可

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
const int INF = 0x3f3f3f3f;

int a[maxn],bit[maxn],n,m; //n:总的区间长度  m:m次操作 

void add(int i,int x){ //给位置i增加x 
    while(i<=n){
        bit[i]+=x;
        i+=i&-i;   //i&-i得到i的二进制最后一个1 
    }
}

void range_add(int l,int r,int x){//给区间[l, r]加上x
    add(l,x),add(r+1,-x);
}

int ask(int i){  //单点查询
    int res=0;
    while(i>0){
        res+=bit[i];
        i-=i&-i;
    }
    return res;
}

int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        add(i,a[i]-a[i-1]);
    }
    while(m--){
        ...;
    }
    return 0;
}

 3. 区间修改 + 区间查询

在区间修改的基础上:

位置p的前缀和:
a[1]+...+a[p]=(d[1])+(d[1]+d[2])+...+(d[1]+...+d[p])
d[1]被用了p次,d[2]被用了p-1次…

我们可以写出:位置p的前缀和 =
d[1]*(p-1+1)+...+d[i]*(p-i+1)+...+d[p]*(p-p+1)
==>(p+1)*(d[1]+...+d[p])-{d[1]*1+...+d[p]*p};

那么我们可以维护两个数组的前缀和:
一个数组是 sum1[i]=d[i]          另一个数组是 sum2[i]=d[i]*i。

查询

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

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

修改

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

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

​
函数模板:

void add(ll i,ll x){ //给位置i增加x 
	while(i<=n){
		sum1[i]+=x;
		sum2[i]+=x*i;
		i+=i&-i;   //i&-i得到i的二进制最后一个1 
	}
}

void range_add(ll l,ll r,ll x){//给区间[l, r]加上x
	add(l,x),add(r+1,-x);
}

ll ask(ll i){  //求位置i的前缀和 
	ll s=0;
	while(i>0){
		s+=(i+1)*sum1[i]-sum2[i];
		i-=i&-i;
	}
	return s;
}

ll range_ask(ll l,ll r){ //区间求和 
	return ask(r)-ask(l-1);
} 

​

   如下是代码模板,只需要根据题意修改‘...’的内容即可

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=1e5+10;
const int INF = 0x3f3f3f3f;

ll n,m;
ll a[maxn];
ll sum1[maxn],sum2[maxn];

void add(ll i,ll x){ //给位置i增加x 
    while(i<=n){
        sum1[i]+=x;
        sum2[i]+=x*i;
        i+=i&-i;   //i&-i得到i的二进制最后一个1 
    }
}

void range_add(ll l,ll r,ll x){//给区间[l, r]加上x
    add(l,x),add(r+1,-x);
}

ll ask(ll i){  //求位置i的前缀和 
    ll s=0;
    while(i>0){
        s+=(i+1)*sum1[i]-sum2[i];
        i-=i&-i;
    }
    return s;
}

ll range_ask(ll l,ll r){ //区间求和 
    return ask(r)-ask(l-1);

int main(){
    scanf("%lld %lld",&n,&m);
    for(ll i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        add(i,a[i]-a[i-1]);
    }
    while(m--){
        ...;
    }
    return 0;
}

用这个做区间修改区间求和的题,无论是时间上还是空间上都比带lazy标记的线段树要优。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值