树状数组总结

因为树状数组用得比较少,所以今天总结一下常见用法。(顺便贴一些板)

首先上树状数组的定义:设原数组为a,树状数组为c,那么c[i]=a[i-2^k+1]+a[i-2^k+2]……+a[i]。其中k为i的二进制状态下最后面连续的0的个数。

然后我们就可以得出树状数组的修改和查询操作了。

  • 查询:因为c[i]表示的是连续的一段a的和,那么查询的时候我们每次把当前的c[i]加上时候就把i减去一段数,这样就可以查询1~i的和了。
  • 修改:我们把a[i]加上k时,影响的是c[i]、c[i+2^k1]、c[(i+2^k1)+2^k2]……(手玩一下就明白了)

下面贴一下查询和修改的板:

int find(int x)
{
	int i=x,s=0;
	while(i>=1){s+=c[i];i=i-(i&(-i));}
	return s;
}
int change(int x,int y)
{
	int i=x;
	while(i<=n){c[i]+=y;i=i+(i&(-i));}
}

 

树状数组的基本操作就在上面了,下面讲一下一些常见的问题。

1、单点修改+区间查询

这个上面已经讲过了

 

2、区间修改+单点查询

这一个我们要用到差分思想:设b[i]=a[i]-b[i-1],而c维护的是b的和而不是a的和,那么区间修改x~y的时候我们就只需要修改x和y+1即可。

代码如下:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAXN 500010

int a[MAXN],c[MAXN],n,m,x,y,k,type;
int find(int x)
{
	int i=x,s=0;
	while(i>=1){s=s+c[i];i=i-(i&(-i));}
	return s;
}
int change(int x,int y)
{
	int i=x;
	if(x==0)return 0;
	while(i<=n){c[i]+=y;i=i+(i&(-i));}
}
int main()
{
int i,j;
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
for(i=1;i<=n;i++)change(i,a[i]-a[i-1]);
while(m>=1)
{
	scanf("%d",&type);
	if(type==1)
	{
		scanf("%d %d %d",&x,&y,&k);
		change(x,k);change(y+1,-k);
	}
	else
	{
		scanf("%d",&x);
		printf("%d\n",find(x));
	}
	m--;
}
}

 

3、区间修改+区间查询

这一个我们还是要用到差分思想,有上面的问题我们的到a[x]=b[1]+b[2]...+b[x],那么我们就有

a[1]+a[2]+a[3]...+a[x]

=b[1]+(b[1]+b[2])+(b[1]+b[2]+b[3])+...+(b[1]+b[2]...+b[x])

=b[1]*x+b[2]*(x-1)+b[3]*(x-2)+...+b[x]*1

=x*(b[1]+b[2]+...+b[x])-(b[1]*0+b[2]*1+b[3]*2+...+b[x]*(x-1))

到这里就十分清晰了。我们只需用两个树状数组,分别维护b[i]的和以及b[i]*(i-1)就可以了。

下面贴一下代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define ll long long
#define MAXN 100010

ll a[MAXN],c1[MAXN],c2[MAXN],n,m,x,y,k;
char type;
ll find1(ll x)
{
	ll i=x,s=0;
	while(i>=1){s+=c1[i];i=i-(i&(-i));}
	return s;
}
ll find2(ll x)
{
	ll i=x,s=0;
	while(i>=1){s+=c2[i];i=i-(i&(-i));}
	return s;
}
int change1(ll x,ll y)
{
	ll i=x;
	while(i<=n){c1[i]+=y;i=i+(i&(-i));}
	return 0;
}
int change2(ll x,ll y)
{
	ll i=x;
	while(i<=n){c2[i]+=y;i=i+(i&(-i));}
	return 0;
}
ll sum(ll x)
{
	return find1(x)*x-find2(x);
}
int main()
{
ll i,j,v;
scanf("%lld %lld\n",&n,&m);
for(i=1;i<n;i++)scanf("%lld ",&a[i]);scanf("%lld\n",&a[n]);
for(i=1;i<=n;i++)
{
	change1(i,a[i]-a[i-1]);
	change2(i,(a[i]-a[i-1])*(i-1));
}
while(m>=1)
{
	scanf("%c ",&type);
	if(type=='C')
	{
		scanf("%lld %lld %lld\n",&x,&y,&k);
		change2(x,k*(x-1));change2(y+1,-k*(y+1-1));
		change1(x,k);change1(y+1,-k);
	}
	else
	{
		scanf("%lld %lld\n",&x,&y);
		printf("%lld\n",sum(y)-sum(x-1));
	}
	m--;
}
return 0;
}

树状数组的常数很小,代码很短,但是处理的范围窄。在做一些有关求和问题的时候不妨用一用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值