树状数组(专题)

目录:

树状数组是什么?

(待补)

模板题

洛谷P3374

#include<bits/stdc++.h>
#include<ext/rope>
#define ll long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define IOS std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)

using namespace std;

const int N = 6e5 + 10;
int n, m;
ll a[N], tree[N];

int lowbit(int x)  //找到x的二进制表示末位1
{
	return x & (-x);
}

void build()  //建树
{
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
		updata(i, a[i]);
	}
}

void updata(int x, int y)  //树的维护
{
	for(; x <= n; x += lowbit(x))
		tree[x] += y;
}
//此处可以如是想:lowbit取出的是当前x的最低含一位
//权值位,相加后等于向高位进位,并且已有的数位永远为零
//这就可以推出:每当x值+=lowbit(x)时,都会有进位,并且
//进位后的新x值一定包含所有原来的x值,也就是说,这一步
//充分地向上进位,达到区间和更新的目的。

ll query(int x)   //查询前缀和
{
	int res = 0;
	for(; x; x -= lowbit(x))
		res += tree[x];
	return res;
}

ll getsum(int x, int y)  //求区间和
{
	return query(y) - query(x - 1);	//这里注意所求区间是包含了x的,所以是减去 x - 1。
}

int main()
{
	cin >> n >> m;
	build();
	while(m --)
	{
		int k;
		int x, y;
		cin >> k >> x >> y;
		if(k == 1)
			updata(x, y);
		else
			cout << getsum(x,y) << endl;  
	}
	return 0;
}

求逆序对(离散化)

首先要知道什么是逆序对:对于一个数组,如果i > j && a[i] < a[j],这两个数就算一对逆序对,简单来说,所有逆序对的个数和就是找每一个数的前面有几个比他的大的数,他们加起来的和就是逆序对的总数。
P1774 最接近神的人

思路详情

我的理解:
离散化之后,每个点的值实际上就是它应该在的位置,比如:
100000 1000 10000 100 10 离散化之后就是:5 3 4 2 1。

我们可以遍历一遍离散化之后的数组,遍历到一个数之后,将树状数组里这个数应该在的位置(也就是离散化后的a【i】)上加一个1(也就是做一个标记,表示这个点已经放入了树状数组),比如遍历到5的话,就将树状数组里的5的位置上+1。

然后计算一下query(5),因为是query(5)是求的1 - 5的区间和,所以query(5)就是当前放入了树状数组里的所有比5小的数个数(包括5自己)。

设 i 是当前已经放了i个数进入树状数组,所以用i - qeury(a[i])就是当前所有比a[i]要大的数的个数,这就是a[i]的逆序对的个数,然后这样遍历一遍数组就可以得到所有的逆序对数。

所以query(5) = 1, i - query(5) = 1 - 1 = 0,ans += 0。

之后的过程如下图:

query(3) = 1, i - query(3) = 2 - 1 = 1, ans += 1。
在这里插入图片描述
query(4) = 2, i - query(4) = 3 - 2 = 1, ans += 1。
在这里插入图片描述
query(2) = 1,i - query(2)= 4 - 1 = 3, ans += 3。
在这里插入图片描述
query(1) = 1,i - query(1)= 5 - 1 = 4,ans += 4。
在这里插入图片描述
所以ans最终的结果是9。

代码:

#include<bits/stdc++.h>
#include<ext/rope>
#define ll long long
#define inf 0x3f3f3f3f
#define endl '\n'
#define IOS std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)

using namespace std;

const int N = 6e5 + 10;
ll n, m, ans;
ll a[N], b[N], tree[N];

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

void updata(int x, int y)  //单点更新
{
	for(; x <= n; x += lowbit(x))
		tree[x] += y;
}

ll query(int x)   //查询前缀和
{
	int res = 0;
	for(; x; x -= lowbit(x))
		res += tree[x];
	return res;
}

bool cmp(int l,int r) //重载运算符用于离散化  
{
    return a[l] < a[r];
}

int main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++)
		cin >> a[i], b[i] = i;
	stable_sort(b + 1, b + n + 1, cmp);
	for(int i = 1; i <= n; i ++)
		a[b[i]] = i;
	for(int i = 1; i <= n; i ++)
	{
		updata(a[i],1); // 标记a【i】
		ans += i - query(a[i]);//到目前为止,一共往树状数组
		//里放入了i个数,而query(a【i】)是树状数组中比a【i】
		//小的数(因为是从左到右遍历的。),所以i - 树状数组中
		//比a【i】小的数就是剩余的比a【i】大的数。
	}
	cout << ans << endl;
    return 0;
}

其他题目

二维树状数组

情人节的电灯泡(树状数组)

#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define fi first
#define se second
#define endl '\n'
#define IOS std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)
using namespace std;

const int N = (1 << 12) + 10;

int n, m, q;

int tree[N][N], t[N][N];

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

void add(int x, int y, int k)
{
	for(int i = x; i <= n; i += lowbit(i))
		for(int j = y; j <= n; j += lowbit(j))
			tree[i][j] += k;
}

int query(int x, int y)
{
	int res = 0;
	for(int i = x; i; i -= lowbit(i))
		for(int j = y; j; j -= lowbit(j))
			res += tree[i][j];
	return res;
}

int main()
{
	cin >> n >> q;
	for(int i = 1; i <= n; i ++)
		for(int j = 1; j <= n; j ++)
		{
			cin >> t[i][j];
			if(t[i][j]) add(i, j, 1);
		}
	while(q --)
	{
		int p;	
		cin >> p;
		if(p == 1)
		{
			int a, b;
			cin >> a >> b;
			if(t[a][b])
				add(a, b, -1), t[a][b] --;
			else
				add(a, b, 1), t[a][b] ++;
		}
		else
		{
			int a, b, c, d;
			cin >> a >> b >> c >> d;
			int ans = query(c, d) - query(a - 1, d) - query(c, b - 1) + query(a - 1, b - 1);
			cout << ans << endl;
		}
	}
	return 0;
}


树状数组实现平衡树

(待补)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值