树状数组
算法 | 单点修改 | 求前缀和 |
---|---|---|
前缀和 | \(O(1)\) | \(O(n)\) |
树状数组 | \(O(logn)\) | \(O(logn)\) |
我们将区间拆分成\([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);
}
}
}
#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];
}
}