2025 Summer Day1
Content:Data Structs
Date:2025.7.17
内容
- 并查集
- ST表
- 线段树
关于树状数组
一维树状数组
单点修改,区间查询
对于这一类最普通的树状数组,没有什么好说的,直接维护前缀和即可。
struct BIT { long long tr[N];
static int lowbit(int x) { return x & (-x); }
void add(int pos, long long value) { for (int i = pos; i <= n; i += lowbit(i)) tr[i] += value; }
long long query(int pos) { long long retval = 0; for (int i = pos; i > 0; i -= lowbit(i)) retval += tr[i]; return retval; }} tr;区间修改,单点查询
这里就需要使用到差分了。 记 ,则我们有:
后面的求和可以用树状数组维护。
struct BIT { long long tr[N];
static int lowbit(int x) { return x & (-x); }
void add(int pos, int value) { for (int i = pos; i <= n; i += lowbit(i)) tr[i] += value; }
long long query(int pos) { long long retval = 0; for (int i = pos; i > 0; i -= lowbit(i)) retval += tr[i]; return retval; }} tr;这一部分的代码和上面的没什么区别,区别只有 函数中的输入和修改。 对于输入:
for (int i = 1; i <= n; i++) { cin >> a[i]; // 这里存储的是差分后的结果,而不是 a[i] tr.add(i, a[i] - a[i - 1]);}对于修改:
int l, r, x; cin >> l >> r >> x;
// 差分数组上的区间价相当于 d[l] += v, d[r + 1] -= v; tr.add(l, x); tr.add(r + 1, -x);区间修改,区间查询
对于区间修改,我们沿用上面的思路,接下来我们看如何区间查询。 差分有如下性质:
而我们要求的区间和可以转化为:
所以我们的问题重新转化为了如何求解前缀和。
所以我们用两个树状数组 来分别维护两个和式,就解决了区间求和的问题。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;int n, q, op, a[N];
int lowbit(int x) { return x & (-x); }
struct BIT { long long tr[N];
void add(int pos, long long value) { for (int i = pos; i <= n; i += lowbit(i)) tr[i] += value; }
long long query(int pos) { long long retval = 0; for (int i = pos; i > 0; i -= lowbit(i)) retval += tr[i]; return retval; }} tr1, tr2;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> q;
for (int i = 1; i <= n; i++) { cin >> a[i];
tr1.add(i, a[i] - a[i - 1]); tr2.add(i, 1ll * (a[i] - a[i - 1]) * i); }
for (int i = 1; i <= q; i++) { cin >> op;
if (op == 1) { int l, r, value; cin >> l >> r >> value; tr1.add(l, value); tr1.add(r + 1, -value); tr2.add(l, 1ll * value * l); tr2.add(r + 1, -1ll * value * (r + 1)); } else if (op == 2) { int l, r; cin >> l >> r;
long long ans = (tr1.query(r) * (r + 1) - tr2.query(r)) - (tr1.query(l - 1) * l - tr2.query(l - 1));
cout << ans << '\n'; } }
return 0;}二维树状数组
Pre:二维前缀和
对于二维数组的前缀和有如下公式:
对于左上角坐标为 ,右下角坐标为 的子矩阵,其和为:
单点修改,区间查询
和一维树状数组类似,直接记 为左上角坐标为 ,右下角坐标为 的矩阵的和。
修改和查询直接套公式。
#include <bits/stdc++.h>
using namespace std;
const int N = 1 << 12 + 5;int n, m, op;
struct BIT { vector<vector<long long>> tr;
BIT() = default;
BIT(int n, int m) { tr.assign(n + 1, vector<long long>(m + 1, 0ll)); }
static int lowbit(int x) { return x & (-x); }
void add(int x, int y, long long value) { for (int i = x; i <= n; i += lowbit(i)) for (int j = y; j <= m; j += lowbit(j)) tr[i][j] += value; }
long long query(int x, int y) { long long retval = 0; for (int i = x; i > 0; i -= lowbit(i)) for (int j = y; j > 0; j -= lowbit(j)) retval += tr[i][j]; return retval; }} tr;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
tr = BIT(n, m);
while (cin >> op) { if (op == 1) { int x, y, value; cin >> x >> y >> value;
tr.add(x, y, value); } else if (op == 2) { int a, b, c, d;
cin >> a >> b >> c >> d;
long long ans = tr.query(c, d) - tr.query(a - 1, d) - tr.query(c, b - 1) + tr.query(a - 1, b - 1);
cout << ans << '\n'; } }
return 0;}区间修改,单点查询
我们由一维树状数组的区间修改,单点查询启发,可以考虑定义二维差分。 定义二维差分数组(为避免名字冲突,这里定义为 )。
证明
观察到:
所以二维差分数组 满足:
即:
所以我们得到了二维差分数组 的表达式。
跟一维差分类似地,我们可以得到子矩阵 , 加法在差分数组中的等效替代:
代码如下:
#include <bits/stdc++.h>
using namespace std;
int n, m, op;
struct BIT { vector<vector<long long>> tr;
BIT() = default;
BIT(int n, int m) { tr.assign(n + 1, vector<long long>(m + 1, 0ll)); }
static int lowbit(int x) { return x & (-x); }
void add(int x, int y, int value) { for (int i = x; i <= n; i += lowbit(i)) for (int j = y; j <= m; j += lowbit(j)) tr[i][j] += value; }
long long query(int x, int y) { long long retval = 0; for (int i = x; i > 0; i -= lowbit(i)) for (int j = y; j > 0; j -= lowbit(j)) retval += tr[i][j]; return retval; }} tr;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
tr = BIT(n, m);
while (cin >> op) { if (op == 1) { int a, b, c, d, value; cin >> a >> b >> c >> d >> value;
tr.add(a, b, value); tr.add(a, d + 1, -value); tr.add(c + 1, b, -value); tr.add(c + 1, d + 1, value); } else if (op == 2) { int x, y; cin >> x >> y;
cout << tr.query(x, y) << '\n'; } }
return 0;}区间修改,区间查询
保留上面二维树状数组上区间加的操作,接下来看看如何实现区间查询。 对于二维差分,我们有:
对于区间查询,我们用前缀和的思路转化为:
而对于二维前缀和,我们有:
所以用四个二维树状数组分别维护 ,,, 就可以了。
#include <bits/stdc++.h>
using namespace std;
int n, m, op;
struct BIT { vector<vector<long long>> t1, t2, t3, t4;
BIT() = default; BIT(int n, int m) { t1.assign(n + 1, vector<long long>(m + 1, 0ll)); t2.assign(n + 1, vector<long long>(m + 1, 0ll)); t3.assign(n + 1, vector<long long>(m + 1, 0ll)); t4.assign(n + 1, vector<long long>(m + 1, 0ll)); }
static int lowbit(int x) { return x & (-x); }
void add(int x, int y, long long value) { for (int i = x; i <= n; i += lowbit(i)) { for (int j = y; j <= m; j += lowbit(j)) { t1[i][j] += value; t2[i][j] += value * x; t3[i][j] += value * y; t4[i][j] += value * x * y; } } }
long long query(int x, int y) { long long retval = 0; for (int i = x; i > 0; i -= lowbit(i)) { for (int j = y; j > 0; j -= lowbit(j)) { retval += (x + 1) * (y + 1) * t1[i][j] - (y + 1) * t2[i][j] - (x + 1) * t3[i][j] + t4[i][j]; } } return retval; }} tr;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
tr = BIT(n, m);
while (cin >> op) { if (op == 1) { int a, b, c, d; long long value; cin >> a >> b >> c >> d >> value;
tr.add(a, b, value); tr.add(c + 1, b, -value); tr.add(a, d + 1, -value); tr.add(c + 1, d + 1, value); } else if (op == 2) { int a, b, c, d; cin >> a >> b >> c >> d;
long long ans = tr.query(c, d) - tr.query(a - 1, d) - tr.query(c, b - 1) + tr.query(a - 1, b - 1);
cout << ans << '\n'; } }
return 0;}
Comments
Quiet notes for this article.