• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
jacklee404
Never Stop!
博客园    首页    新随笔    联系   管理    订阅  订阅
树状数组学习

树状数组

算法 单点修改 求前缀和
前缀和 \(O(1)\) \(O(n)\)
树状数组 \(O(logn)\) \(O(logn)\)

image-20230328203334125

​ 我们将区间拆分成\([r - lowbit(r) + 1, r]\), 如上图所示,我们用右端点\(r\)来表示一个区间, 则\(r_1\)节点的父亲节点为\(r_1 + lowbit(r_1)\), 由于树状数组的每个节点到其祖宗节点是一条单链结构,所以对于单点修改,我们可以从儿子节点通过\(+lowbit(i)\) 枚举到父亲节点, 对于求前缀和操作我们只需要从\(x\)通过\(- lowbit(i)\) 依次向上枚举。

扩展

区间修改+单点查询

​ 由于树状数组可以\(O(logn)\)时间内完成求前缀操作,所以我们可以用树状数组维护一个差分数组, 求前缀即为第i个数的增加或减少值

区间修改+区间求和

​ 假设我们用树状数组来维护一个差分序列,则对于第i个数的数值,我们需要求

\[\sum \limits_{i = 1}^x \sum \limits_{j = 1}^i b_j \\ = (x + 1) \times (b_1 + b_2 + \dots + b_x) + (b_1 + 2b_2 + 3b_3 + \dots + xb_x) \\ = (x + 1) \sum \limits_{i = 1}^x b_i + \sum \limits_{i = 1}^xib_i \]

​ 通过上式,我们只需要用树状数组维护\(b_i\) 和\(ib_i\)的值即可, 通过求前缀操作便可以\(O(logn)\)时间内完成查询和修改操作

求逆序对

例题

一个简单的整数问题

#include <iostream>
#define lowbit(x) (x & -x)

const int N = 1e5 + 10;

using i64 = long long;

int n, m, a[N];
i64 tr[N];

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

i64 ask(int x) {
	i64 res = a[x];
	for (int i = x; i; i -= lowbit(i)) res += tr[i];
	return res;
}

int main() {
	std::cin >> n >> m;

	for (int i = 1; i <= n; i ++) std::cin >> a[i];
	
	while (m --) {
		std::string op;
		int x, l, r;

		std::cin >> op;

		if (op == "Q") {
			std::cin >> x;
			std::cout << ask(x) << "\n";
		} else {
			std::cin >> l >> r >> x;
			add(l, x);
			add(r + 1, -x);
		}
	}
}

一个简单的整数问题2

#include <iostream>
#define lowbit(x) (x & -x)
using i64 = long long;

const int N = 1e5 + 10;

int n, m, a[N];

i64 tr1[N], tr2[N];

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

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

i64 prefix(int x) {
	return query(tr1, x) * (x + 1) - query(tr2, x);
}

int main() {
	std::cin >> n >> m;

	for (int i = 1; i <= n; i ++) {
		std::cin >> a[i];
		add(tr1, i, a[i] - a[i - 1]);
		add(tr2, i, (i64)i * (a[i] - a[i - 1]));
	}

	while (m --) {
		char op[2];
		int l, r, x;

		scanf("%s%d%d", op, &l, &r);

		if (op[0] == 'C') {
			scanf("%d", &x);
			add(tr1, l, x), add(tr1, r + 1, -x);
			add(tr2, l, (i64) l * x), add(tr2, r + 1, (i64) (r + 1) * -x);
		} else {
			std::cout << prefix(r) - prefix(l - 1) << "\n";
		}
	}
}

谜一样的牛

#include <iostream>
#define lowbit(x) (x & -x)
using i64 = long long;

const int N = 1e5 + 10;

int n, tr[N], a[N], ans[N];

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

int query(int x) {
	int res = 0;
	for (int i = x; i; i -= lowbit(i)) res += tr[i];
	return res;
}

int main() {
	std::cin >> n;

	for (int i = 2; i <= n; i ++) std::cin >> a[i];

	for (int i = 1; i <= n; i ++) tr[i] = lowbit(i);
	
	for (int i = n; i >= 1; i --) {
		int k = a[i] + 1;
		int l = 1, r = n;
		while (l < r) {
			int mid = l + r >> 1;
			if (query(mid) >= k) r = mid;
			else l = mid + 1;
		}
		add(r, -1);
		ans[i] = r;
	}

	for (int i = 1; i <= n; i ++) {
		std::cout << ans[i] << " \n"[i == n];
	}
}
posted on 2023-03-28 21:03  Jack404  阅读(11)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3