树状数组模板总结

1. 一维树状数组的应用场景

解决涉及一维区间的在线操作问题。(如单点修改 + 区间查询、区间修改 + 单点查询、区间修改 + 区间查询)

1.1单点修改 + 区间查询

例题传送门

inline int lowbit(int x) // 求二进制下最低位的1
{
    return x & -x;
}

inline void add(int x, int d) //单点修改  给第x号元素 + d
{
    while (x <= n) {
        aa[x] += d;
        x += lowbit(x);
    }
}

inline int ask(int x) // 求从第一个元素 到 第x个元素的总和
{
    int res = 0;
    while (x) {
        res += aa[x];
        x -= lowbit(x);
    }
    return res;
}
inline int getsum(int x, int y) // 求区间[l, r]内的元素总和
{
	return ask(r) - ask(l - 1);
}

1.2区间修改 + 单点查询

例题传送门
原理:利用差分的思想对区间[l, r]在O(1)的时间复杂度内进行修改

inline int lowbit(int x) // 求二进制下最低位的1
{
	return x & -x;
}

void add(int x, int d) //修改点
{
	while (x <= n) {
		p[x] += d;
		x += lowbit(x);
	}
}

void range_add(int l, int r, int d) //经过差分处理后, p数组修改只要在第l个元素的 +d 和第r + 1个元素-d即可完成区间修改
{
	add(l, d); add(r + 1, -d); 
}

int ask(int x) //求第x个位置的元素值
{
	int res = 0;
	while (x) {
		res += p[x];
		x -= lowbit(x);
	}
	return res;
}

1.3区间修改 + 区间查询

原理:利用差分的思想对区间进行修改,再通过数学公式的推导得出如何对区间进行维护。
通过对sum1数组,sum2数组的维护对区间[l, r]进行查询。
例题传送门

typedef long long ll;
ll sum1[N], sum2[N];

inline int lowbit(int x) // 求二进制下最低位的1
{
	return x & -x;
}

inline void add(ll x, ll d) // 对sum1数组和sum2数组进行维护
{
	for (int i = x; i <= n; i += lowbit(i)) {
		sum1[i] += d; sum2[i] += x * d;
	}
}
inline void range_add(ll l, ll r, ll d) // 差分思想进行区间维护
{
	add(l, d); add(r + 1, -d);
}

inline ll ask(ll x) // 求[1, x]内的所有元素之和
{
	ll res = 0;
	for (int i = x; i > 0; i -= lowbit(i)) {
		res += (x + 1) * sum1[i] - sum2[i];
	}
	return res;
}

inline ll range_ask(ll l, ll r) //求区间[l, r]内所有元素之和
{
	return ask(r) - ask(l - 1);
}

1.4树状数组其他神奇操作

1.4.1求逆序对数量

用树状数组解逆序对用到一个技巧:把数字看成树状数组的下标。每处理一个数字,树状数组的下标所对应的元素数值加一,统计前缀和,就是逆序对的数量。倒序或正序处理数据都行。(处理前要离散化)
例题传送门

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

typedef long long ll; 
const int N = 5e5 + 7;

int n;
int tr[N], a[N];
vector<int> mp;
ll ans;

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

void add(int x, int d)
{
	while (x <= n) {
		tr[x] += d;
		x += lowbit(x);
	}
}

int ask(int x)
{
	int res = 0;
	while (x) {
		res += tr[x];
		x -= lowbit(x);
	} 	
	return res;
}

inline int get_id(int x)
{
	return lower_bound(mp.begin(), mp.end(), x) - mp.begin() + 1;
}

int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), mp.push_back(a[i]);
	//离散化
	sort(mp.begin(), mp.end());
	mp.erase(unique(mp.begin(), mp.end()), mp.end());
	// 先序遍历找这个点前有多少个点比它大
	for (int i = 1; i <= n; ++i) {
		int u = get_id(a[i]);
		add(u, 1);
		ans = ans + i - ask(u);
	}
	/*
	//后序遍历,看在这个点后有多少个点比它小
	for (int i = n; i > 0; --i) {
		int u = get_id(a[i]);
		add(u, 1);
		ans += ask(u - 1); 
	*/
	printf("%lld\n", ans);
	return 0;
}

 

2. 二维树状数组

2.1 单点修改 + 二维区间查询

其实和一维梳妆数组差不多,只是把一维变成二维,多了重y轴方向for循环。

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

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

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

int range_ask(int x1, int y1, int x2, int y2)
{
    return ask(x2, y2) - ask(x1 - 1, y2) - ask(x2, y1 - 1) + ask(x1 - 1, y1  - 1);
}

2.1 二维区间修改 + 单点查询

利用二维差分的思想,进行区间修改,然后用树状数组求和进行单点查询。

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

void update(int x, int y, int d) //单点修改
{
	for (int i = x; i <= n; i += lowbit(i))
		for (int j = y; j <= m; j += lowbit(j));
			tree[i][j] += d;
}

void ranger_update(int x1, int y1, int x2, int y2, int d) //二维区间修改
{
	update(x1, y1, d);
	update(x1, y2 + 1, -d);
	update(x2 + 1, y1, -d);
	update(x2 + 1, y2 + 1, d);
}

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

2.1 二维区间修改 + 二维区间查询

利用差分的思想,进行区间修改,然后用二维前缀和的思想+树状数组进行区间查询。

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

void update(int x, int y, int d) //单点修改
{
	for (int i = x; i <= n; i += lowbit(i))
		for (int j = y; j <= m; j += lowbit(j));
			tree[i][j] += d;
}

void ranger_update(int x1, int y1, int x2, int y2, int d) //二维区间修改
{
	update(x1, y1, d);
	update(x1, y2 + 1, -d);
	update(x2 + 1, y1, -d);
	update(x2 + 1, y2 + 1, d);
}

int ask(int x, int y) // 单点查询
{
	int res = 0;
	for (int i = x; i > 0; i -= lowbit(i))
		for (int j = y; j > 0; j -= lowbit(j))
			res += tree[i][j];
	return res;
}

int ranger_ask(int x1, int y1, int x2, int y2) //利用二维差分进行二维区间查询
{
	return ask(x2, y2) - ask(x2, y1 -1) - ask(x1 - 1, y2) + ask(x1 - 1, y1 - 1);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值