树状数组学习笔记1

        树状数组的就相当于局部内前缀和,根据上图来理解,下面的A是树状数组,上面橙色部分是树状数组存的东西,灰线对应下来树状数组的位置,比如说A[2]存的是a[1],a[2]的和,A[9]放的是a[9]的值,修改值的话就是修改灰线对应上去的橙色格子,+k或者-k

        那么你们一定很好奇他是怎么求前缀和的,他用到一个lowbit的函数(x & -x),这个原理不用很清楚,只需要知道他可以求二进制的最后一位非0位,比如说,110,得到的是第二位1,也就是10,   1010,也是得到10

        举个栗子,想要知道a[1]+a[2]+...a[9]的值,那么先从A[9]开始,得到a[9],接着lowbit(9),9是1001得到1,9-1等于8,下一次加上A[8]的值,8不能继续拆了,到此终止循环,那么a[1]+a[2]+...+a[9]的和就是A[8]+A[9],具体见下面程序

        这样的话,求动态数组的前缀和由复杂度O(n²)降为O(nlogn)

        树状数组一般来说可以解决(单点修改,区间查询)和(区间修改,单点查询)的问题,以下是两个例题

P3374 【模板】树状数组 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

代码:单点修改,区间查询
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 5e5+10;
int a[N],c[N],n;
int lowbit(int x)
{
	return x & -x;
}
void add(int x,int k) // 每一次更新的值是对应该灰线上方的橙色格子
{
	for (int i = x; i <= n; i += lowbit(i)) c[i] += k;
}
int sum(int x) // 求c[1]+c[2]+...+c[x]
{
	int ret = 0;
	for (int i = x; i > 0; i -= lowbit(i)) ret += c[i];
	return ret;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	int m;
	cin >> n >> m; 
	for (int i = 1; i <= n; ++i) 
	{
		cin >> a[i];
		add(i,a[i]);
	}
	while(m--)
	{
		int op,x,k,l,r;
		cin >> op;
		if (op == 1)
		{
			cin >> x >> k;
			add(x,k);
		}
		else if (op == 2)
		{
			cin >> l >> r;
			cout << sum(r) - sum(l-1) << endl; // 前缀和思想
		}
	}
	return 0;
}

 P3368 【模板】树状数组 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

代码:区间修改,单点查询
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N = 5e5+10;
int a[N],c[N],n;
int lowbit(int x)
{
	return x & -x;
}
void add(int x,int k) // 维护一个差分数组
{
	for (int i = x; i <= n; i += lowbit(i)) c[i] += k;
}
int sum(int x) // 求c[1]+c[2]+...+c[x]
{
	int ret = 0;
	for (int i = x; i > 0; i -= lowbit(i)) ret += c[i];
	return ret;
}
// 区间修改,单点查询
// 维护一个差分的前缀和即可 
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	int m;
	cin >> n >> m; 
	for (int i = 1; i <= n; ++i) cin >> a[i];

	while(m--)
	{
		int op,x,y,k;
		cin >> op;
		if (op == 1)
		{
			cin >> x >> y >> k;
			if (x > y) swap(x,y);
			add(x,k); // 差分思想,区间修改可以求差分数组的前缀和
			add(y+1,-k);
		}
		else if (op == 2)
		{
			cin >> x;
			cout << a[x] + sum(x) << endl;
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值