专题训练_树状数组

题意

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 xx

  • 求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。

接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:

  • 1 x k 含义:将第 x 个数加上 k

  • 2 x y 含义:输出区间 [x,y]内每个数的和

输出格式

输出包含若干行整数,即为所有操作 22 的结果

思路

这题我用来作为树状数组的理解和学习吧

首先是lowbit函数 比如说 5 (101) 可以通过函数转换变成 1 (001)就是取出 第一个1这样就可以对数组进行一个二进制化的操作

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

比如说

 

c[1]=a[1]

c[2]=a[1]+a[2] (c[0010]=a[0001]+a[0010])

c[3]=a[3] (c[0011]=a[0011])

c[4]=c[2]+c[3]+a[4] (c[0100]=a[0100]+c[0011]+c[0010])

所以换一种看法就是 比如说 改变a[1]的值,那么将会影响到的是 c[1] c[2] c[4] c[8] c[16]

如果改变a[3]的值,那么将会影响到的是 c[3] c[4] c[8] c[16]

可以发现的是 改变a[n]的值 就会 改变 c[n] c[n+lowbit(n)] ......

所以可以写出一个单点改变的函数就是

void add(int x,int d)
{
	a[x]+=d;
	for(int i=x;i<=n;i+=lowbit(i))
		c[i]+=d;
}

那么再要查找呢,如果要我们找[L,R]的和呢

可以分析出要求某一个区间和,是[1,R]-[1,L]这两个区间的和相减的

然后怎么求[1,x]的区间和呢

这就要发现一个规律,结合我们第一张图

比如搜索sum(7)则 sum=c[7]+c[6]+c[4]

7 -> 6 (7-lowbit(7))

6 -> 4   (6-lowbit(6))

所以可以写出来一个求和式子

long long sum(int u)
{
    int ans=0;
    for(int i=u;i>0;i-=lowbit(i))
    ans+=c[i];
    return ans;
}

题目

#include<iostream>
using namespace std;
typedef long long ll;
const int maxn=5e5+10; 
ll n,m;
ll a[maxn];
ll c[maxn];
ll lowbit(int x)
{
	return x&(-x);
}
ll sum(ll u)
{
    ll ans=0;
    for(int i=u;i>0;i-=lowbit(i))
    	ans+=c[i];
    return ans;
}
void add(ll x,ll d)
{
	a[x]+=d;
	for(int i=x;i<=n;i+=lowbit(i))
		c[i]+=d;
}
int main()
{
	ios::sync_with_stdio(false);cin.tie(0);
	cin>>n>>m;
	int tmpn=n;
	for(int i=1;i<=n;i++)
	{
	  int x;cin>>x;
	  add(i,x);
	}
	while(m--)
	{
		int p;cin>>p;
		if(p==1)
		{
			int x1,k;cin>>x1>>k;
			add(x1,k);
		}
		if(p==2)
		{
			int l,r;cin>>l>>r;
			ll ans=sum(r)-sum(l-1);
			cout<<ans<<endl;
		}
	}
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组(Fenwick Tree)是一种用于快速维护数组前缀和的数据结构。它可以在 $O(\log n)$ 的时间内完成单点修改和前缀查询操作,比线段更加简洁效。 下面是 Java 实现的树状数组详解: 首先,在 Java 中我们需要使用数组来表示树状数组,如下: ``` int[] tree; ``` 接着,我们需要实现两个基本操作:单点修改和前缀查询。 单点修改的实现如下: ``` void update(int index, int value) { while (index < tree.length) { tree[index] += value; index += index & -index; } } ``` 该函数的参数 `index` 表示要修改的位置,`value` 表示修改的值。在函数内部,我们使用了一个 `while` 循环不断向上更新树状数组中相应的节点,直到到达根节点为止。具体来说,我们首先将 `tree[index]` 加上 `value`,然后将 `index` 加上其最后一位为 1 的二进制数,这样就可以更新其父节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,加上后变为 111,即 7,这样就可以更新节点 7 了。 前缀查询的实现如下: ``` int query(int index) { int sum = 0; while (index > 0) { sum += tree[index]; index -= index & -index; } return sum; } ``` 该函数的参数 `index` 表示要查询的前缀的结束位置,即查询 $[1, index]$ 的和。在函数内部,我们同样使用了一个 `while` 循环不断向前查询树状数组中相应的节点,直到到达 0 为止。具体来说,我们首先将 `sum` 加上 `tree[index]`,然后将 `index` 减去其最后一位为 1 的二进制数,这样就可以查询其前一个节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,减去后变为 100,即 4,这样就可以查询节点 4 的值了。 最后,我们还需要初始化树状数组,将其全部置为 0。初始化的实现如下: ``` void init(int[] nums) { tree = new int[nums.length + 1]; for (int i = 1; i <= nums.length; i++) { update(i, nums[i - 1]); } } ``` 该函数的参数 `nums` 表示初始数组的值。在函数内部,我们首先创建一个长度为 `nums.length + 1` 的数组 `tree`,然后逐个将 `nums` 中的元素插入到树状数组中。具体来说,我们调用 `update(i, nums[i - 1])` 来将 `nums[i - 1]` 插入到树状数组的第 `i` 个位置。 到此为止,我们就完成了树状数组的实现。可以看到,树状数组的代码比线段要简洁很多,而且效率也更

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值