20260112 - 树状数组
前言
第一次 Rank 1 呢!
树状数组
基本概念
树状数组是一种高效维护动态前缀和(动态前缀最值)的数据结构,支持:
- 单点更新 \(O(\log_2n)\)
- 前缀求和 \(O(\log_2n)\)
空间复杂度:\(O(n)\)
优势:
- 代码简洁( \(<20\)行代码)
- 常数因子小(相比线段树)
- 内存效率高(相比线段树)
劣势:
- 无法操作区间翻转等复杂操作。
- 处理单点修改 \(\max\) 需要 \(\log_2^2 n\) 的时间,不如线段树。
- 没有分块灵活。
结构图
8
/ \
4 12
/ \ / \
2 6 10 14
/ \ / \ / \ / \
1 3 5 7 9 11 13 15
思想
可以知道,任意一个数都可以表示为至多 \(\log_2\) 个 \(2\) 的幂的和,这样子就可以让单点区间修改从 \(O(n)\) 优化到 \(O(\log_2n)\)。
快速幂,倍增都是这样的思想。
lowbit
这是树状数组聪明的地方!
在树状数组中,我们利用 lowbit(二进制表示中最小的1)来实现这种操作。
6 的 lowbit 是 2,8 的 lowbit 是 8,可以用 x & -x 来快速计算。
树状数组封装代码
template <class T> struct BIT {
vector <T> c;
int n;
BIT (int n_) {
n = n_;
c.resize(n + 1);
}
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
使用方法
和 vector 很像,使用时直接用 BIT <T> bit,T 代指数据类型(int, long long 等)。
例题
A - 求区间和
前缀和?分块?莫队?不不不,是线段树树状数组!
用上面的搞一搞就好了!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;
template<class T> struct BIT {
vector <T> c;
int n;
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
int n, m, a[N];
void solve() {
cin >> n;
rep (i, 1, n) cin >> a[i];
BIT <int> bit;
bit.build(n, a);
cin >> m;
while (m--) {
int op, l, r;
cin >> l >> r;
cout << bit.query(l, r) << endl;
}
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
B - 树状数组 1
树状数组模版!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;
template<class T> struct BIT {
vector <T> c;
int n;
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
int n, m, a[N];
void solve() {
cin >> n >> m;
rep (i, 1, n) cin >> a[i];
BIT <int> bit;
bit.build(n, a);
while (m--) {
int op, l, r;
cin >> op >> l >> r;
if (op == 1) {
bit.update(l, r);
}else {
cout << bit.query(l, r) << endl;
}
}
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
C - 逆序对
使用权值树状数组,维护每个数出现的次数,
在统计第 \(i\) 个数 \(a[i]\) 对逆序对的贡献时,查询大于 \(a[i]\) 的数有多少个即可,
然后把 \(a[i]\) 的出现次数 \(+1\)。
注意:离散化
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;
template<class T> struct BIT {
vector <T> c;
int n;
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
int n, a[N], b[N];
map <int, int> mp;
void solve() {
BIT <ll> bit;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + n + 1);
int idx = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= idx; i++)
mp[b[i]] = i;
bit.build(n);
ll ans = 0;
for (int i = 1; i <= n; i++) {
int id = mp[a[i]];
ans += bit.query(id + 1, idx);
bit.update(id, 1);
}
cout << ans << endl;
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
D - 树状数组 2
用树状数组维护原序列的差分数组,差分数组的前缀和就是原序列的值。
所以 \([l, r]\) 区间整体 \(+x\),就是在树状数组上的 \(l\) 位置 \(+x\),\(r+1\) 位置 \(-x\),
查询原序列的第 \(i\) 个元素,就是在树状数组上求前 \(i\) 项之和。
注意:如果 \(r + 1\) 大于 \(n\),则不减,因为会越界!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;
template<class T> struct BIT {
vector <T> c;
int n;
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
int n, m, a[N];
void solve() {
cin >> n >> m;
rep (i, 1, n) cin >> a[i];
BIT <int> bit;
bit.build(n);
while (m--) {
int op, l, r, k;
cin >> op >> l;
if (op == 1) {
cin >> r >> k;
bit.update(l, k);
if (r + 1 <= n) bit.update(r + 1, -k);
}else {
cout << bit.query(1, l) + a[l] << endl;
}
}
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
E - 中位数
方法一:堆。。。
方法二:树状数组。。。
又是一个权值树状数组,二分去查询前缀的值是否为 \(\lfloor\dfrac{n}{2} \rfloor + 1\),二分作死请见 ST 表总结!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e5 + 7;
constexpr int P = 998244353;
template<class T> struct BIT {
vector <T> c;
int n;
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
int n, a[N], b[N], val[N];
map <int, int> mp;
void solve() {
BIT <int> bit;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
}
bit.build(n);
sort(b + 1, b + n + 1);
int idx = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1;i <= idx;i++)
mp[b[i]] = i, val[i] = b[i];
for (int i = 1; i <= n; i++) {
int id = mp[a[i]];
bit.update(id, 1);
if (i & 1) {
int l = 0, r = idx + 1;
while(l + 1 < r) {
int mid = (l + r) / 2;
if (bit.query(1, mid) >= i / 2 + 1) {
r = mid;
}else {
l = mid;
}
}
cout << val[r] << endl;
}
}
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
F - MooFest G
有绝对值,很讨厌,遇到这种题目,直接分类讨论:
- \(a_i - a_j > 0\)
- \(a_i - a_j \le 0\)
在统计求和即可!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 2e4 + 7, MAXN = 2e4;
constexpr int P = 998244353;
int n;
vector <pair <int, int>> ve;
template<class T> struct BIT {
vector <T> c;
int n;
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
void solve() {
BIT <ll> t1, t2;
cin >> n;
ve.resize(n);
t1.build(MAXN);
t2.build(MAXN);
for (int i = 0; i < n; i++) cin >> ve[i].first >> ve[i].second;
sort(ve.begin(), ve.end());
ll ans = 0, cnt = 0;
for (auto [v, x] : ve) {
// cout << v << " " << x << endl;
ll res = v * ((x * t1.query(1, x)) - t2.query(1, x));
ll ret = v * (t2.query(1, MAXN) - t2.query(1, x) - (cnt - t1.query(1, x)) * x);
ans += res + ret;
++cnt;
// cout << (t1.query(1, x)) << " " << ret << endl;
t1.update(x, 1);
t2.update(x, x);
}
cout << ans << endl;
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
G - Balanced Lineup G
老师曾没讲的树状数组求区间最值!
老师上课时,yxc 问老师,是不是只用数据结构就行,老师没说话,yxc 以为默认了,所以就有了线段树做法!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e6 + 7;
constexpr int P = 998244353;
int n, q, a[N], t[N * 4], f[N * 4];
void build(int id, int l, int r) {
if (l == r) {
t[id] = a[l];
f[id] = a[l];
return;
}
int mid = l + (r - l) / 2;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
t[id] = max(t[id * 2], t[id * 2 + 1]);
f[id] = min(f[id * 2], f[id * 2 + 1]);
}
int calc(int id, int l, int r, int ql, int qr) {
if (r < ql || l > qr) return INT_MAX;
if (ql <= l && r <= qr) return f[id];
int mid = l + (r - l) / 2;
return min(calc(id * 2, l, mid, ql, qr), calc(id * 2 + 1, mid + 1, r, ql, qr));
}
int query(int id, int l, int r, int ql, int qr) {
if (r < ql || l > qr) return 0;
if (ql <= l && r <= qr) return t[id];
int mid = l + (r - l) / 2;
return max(query(id * 2, l, mid, ql, qr) ,query(id * 2 + 1, mid + 1, r, ql, qr));
}
void solve() {
memset(f, 0x3f, sizeof(f));
cin >> n >> q;
rep (i, 1, n) cin >> a[i];
build(1, 1, n);
while (q--) {
int x, y;
cin >> x >> y;
cout << query(1, 1, n, x, y) - calc(1, 1, n, x, y) << endl;
}
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
H - LIT-Letters
这可能是常数最大的做法吧!
我们因为要记录每一个字母出现的位置,当时不知道为什么,写了一个常数倍增的做法,但好像很好懂!
剩下的就是逆序对了!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e6 + 7;
constexpr int P = 998244353;
template<class T> struct BIT {
vector <T> c;
int n;
int lowbit (int x) {
return x & -x;
}
void update (int x, T val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
T query (int x) {
T res = 0;
while (x > 0) {
res += c[x];
x -= lowbit(x);
}
return res;
}
T query (int l, int r) {
return query(r) - query(l - 1);
}
void build (int n_, T a[]) {
n = n_;
c.resize(n + 1);
for (int i = 1; i <= n; i++)
update(i, a[i]);
}
void build (int n_) {
n = n_;
c.resize(n + 1);
}
~BIT() {
c.clear();
}
};
int n, a[N], b[N];
map <int, int> mp;
void solve() {
BIT <ll> bit;
cin >> n;
array <deque<int>, CHAR_MAX> cnt; // 常数大大大
string aa, bb;
cin >> aa >> bb;
for (int i = 1; i <= n; i++) {
cnt[aa[i - 1]].push_back(i);
}
rep (i, 1, n) {
a[i] = cnt[bb[i - 1]].front();
cnt[bb[i - 1]].pop_front();
b[i] = a[i];
}
sort(b + 1, b + n + 1);
int idx = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= idx; i++)
mp[b[i]] = i;
bit.build(n);
ll ans = 0;
for (int i = 1; i <= n; i++) {
int id = mp[a[i]];
ans += bit.query(id + 1, idx);
bit.update(id, 1);
}
cout << ans << endl;
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
I - Sound 静音问题
和 G 一样,被 yxc 的默认给坑了!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
typedef pair<int, int> PII;
constexpr int N = 1e6 + 7;
constexpr int P = 998244353;
int n, m, c, a[N], t[N * 4], f[N * 4];
void build(int id, int l, int r) {
if (l == r) {
t[id] = a[l];
f[id] = a[l];
return;
}
int mid = l + (r - l) / 2;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
t[id] = max(t[id * 2], t[id * 2 + 1]);
f[id] = min(f[id * 2], f[id * 2 + 1]);
}
int calc(int id, int l, int r, int ql, int qr) {
if (r < ql || l > qr) return INT_MAX;
if (ql <= l && r <= qr) return f[id];
int mid = l + (r - l) / 2;
return min(calc(id * 2, l, mid, ql, qr), calc(id * 2 + 1, mid + 1, r, ql, qr));
}
int query(int id, int l, int r, int ql, int qr) {
if (r < ql || l > qr) return 0;
if (ql <= l && r <= qr) return t[id];
int mid = l + (r - l) / 2;
return max(query(id * 2, l, mid, ql, qr) ,query(id * 2 + 1, mid + 1, r, ql, qr));
}
void solve() {
memset(f, 0x3f, sizeof(f));
cin >> n >> m >> c;
rep (i, 1, n) cin >> a[i];
build(1, 1, n);
int cnt = 0;
rep (i, 1, n - m + 1) {
if (query(1, 1, n, i, i + m - 1) - calc(1, 1, n, i, i + m - 1) <= c) {
++cnt;
cout << i << '\n';
}
}
if (!cnt) cout << "NONE" << endl;
}
int main() {
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
J - Mishka and Interesting sum
嗯,CF 又炸了!
原来是求出现偶数次的单个数的异或和。
通过凑样例可以发现,答案是求区间内不同数的异或和与区间异或和的异或和。
那么现在问题就变为维护区间内不同的数的异或和
这题数据范围的话,需要离散化。
这个好像离线二维数点的题目,但是,如果出现过,就直接删除。
本来想用指针的,发现变量的生命是如此的短暂,导致了指向了一个空指针!
#include <bits/stdc++.h>
using namespace std;
#define rep(i, x, y) for (int i = (x); i <= (y); i++)
#define per(i, x, y) for (int i = (x) ;i >= (y); i--)
#define ll long long
#define ull unsigned long long
#define db double
#define sz(x) ((int)x.size())
#define inf (1 << 30)
#define pb push_back
#define lowbit(x) (x & -x)
typedef pair<int, int> PII;
constexpr int N = 1e6 + 7;
constexpr int P = 998244353;
int n, m, a[N], b[N], s[N], c[N];
int head[N], pre[N];
struct Node {
int l, r, id;
};
vector <Node> ve;
void add(int x, int val) {
while (x <= n) {
c[x] ^= val;
x += lowbit(x);
}
}
int query(int x) {
int res = 0;
while (x) {
res ^= c[x];
x -= lowbit(x);
}
return res;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + n + 1);
int idx = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= idx; i++) head[i] = 0;
for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + idx + 1, a[i]) - b;
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] ^ b[a[i]];
// if (head[a[i]])
// pre[i] = *head[a[i]];
// else
// pre[i] = 0;
// head[a[i]] = &i;
pre[i] = head[a[i]];
head[a[i]] = i;
}
cin >> m;
for (int i = 1; i <= m; i++) {
int l, r;
cin >> l >> r;
ve.pb({l, r, i});
}
sort(ve.begin(), ve.end(), [&](const Node &x, const Node &y) {
return x.r < y.r;
});
int now = 1;
vector <int> ans(m);
for (auto [l, r, id] : ve) {
while (now <= r) {
if (pre[now]) add(pre[now], b[a[now]]);
add(now, b[a[now]]);
now++;
}
ans[id - 1] = (query(r) ^ query(l - 1)) ^ (s[r] ^ s[l - 1]);
}
for (auto v : ans) cout << v << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int oT_To = 1;
// cin >> oT_To;
while (oT_To--) solve();
return 0;
}
警示后人:没事不要用指针,看着不好用,实际上也不是很好用!(除非闲的没事干)
后记
树状数组可以解决带修前缀问题,是一个很好用但局限性很高的数据结够。总而言之,码量完胜线段树,适用场景被线段树吊打!

浙公网安备 33010602011771号