数据结构
树状数组
#include <bits/stdc++.h> using namespace std; #define ll long long #define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) const int N = 2e6 + 10; ll n, m, a[N], tr[N]; int lowbit(int x) { return x & -x; } // 单点修改 // a[x] 加上 c void add(int x, int c) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += c; } // 区间查询 // 查询[1, x] 的区间和 ll query(int x) { ll ans = 0; for (int i = x; i; i -= lowbit(i)) ans += tr[i]; return ans; } void solve() { cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], add(i, a[i]); while (m--) { int op, x, y; cin >> op >> x >> y; if (op == 1) add(x, y); else cout << query(y) - query(x - 1) << "\n"; } } int main() { qwq; int T = 1; // cin >> T; while (T--) solve(); return 0; }
练习:U305748 楼兰图腾 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
#include <bits/stdc++.h> using namespace std; #define ll long long #define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) const int N = 2e5 + 10, P = 998244353, INF = 0x3f3f3f3f; int lowbit(int x) { return x & -x; } int tr[N], n; void add(int x, int k) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += k; } ll query(int x) { ll sum = 0; for (int i = x; i; i -= lowbit(i)) sum += tr[i]; return sum; } void solve() { cin >> n; vector<int> a(n), l(n), r(n), L(n), R(n); // l[i] 表示第 i 个位置左侧有几个 正序对 // L[i] 表示第 i 个位置左侧有几个 逆序对 / for (int i = 0; i < n; ++i) cin >> a[i]; for (int i = 0; i < n; ++i) { add(a[i], 1); l[i] = query(a[i] - 1); L[i] = query(n) - query(a[i]); } memset(tr, 0, sizeof tr); for (int i = n - 1; i >= 0; --i) { add(a[i], 1); r[i] = query(n) - query(a[i]); R[i] = query(a[i] - 1); } // V 左侧为逆序对 右侧为正序对 ll p = 0, q = 0; for (int i = 0; i < n; ++i) { // cout << L[i] << " " << r[i] << " " << l[i] << " " << R[i] << "\n"; p += L[i] * r[i]; q += l[i] * R[i]; } cout << p << " " << q; } int main() { qwq; int T = 1; // cin >> T; while (T--) solve(); return 0; }
线段树
线段树可以在 O( logN ) 的时间复杂度内实现单点修改、区间修改、区间查询(区间求和,求区间最大值,求区间最小值)等操作。
注意点:
- 线段树的数组一般要开到4 * N; 位运算的写法为 N >> 2
- 对于懒标记:修改的时候不用用到下面的区间,查询的时候才会用到下面的区间
- 故每次插入懒标记不用递归到叶子节点
建树
void build(int u, int l, int r) { if (l == r) { // 递归边界 f[u] = a[l]; return; } int mid = l + r >> 1; build(u << 1, l, mid); // 建左儿子 build(u << 1 | 1, mid + 1, r); f[u] = f[u << 1] + f[u << 1 | 1]; // 父节点区间和 = 左儿子区间和 + 右儿子区间和 }
单点修改 & 区间查询 (无懒标记)
可以用树状数组解决此类问题
// 当前修改 u 节点, u 节点所对应区间为[l, r] 要给 a[p] 加上 c void add(int u, int l, int r, int p, int c) { f[u] += c; if (l == r) return; int mid = l + r >> 1; if (p <= mid) // p 在左儿子区间 add(u << 1, l, mid, p, c); else add(u << 1 | 1, mid + 1, r, p, c); }
// 当前为 u 节点, u 节点所对应区间为[l, r] 要查询[s, t]区间和 ll query(int u, int l, int r, int s, int t) { if (l == s && r == t) return f[u]; int mid = l + r >> 1; if (t <= mid) // 查询区间完全在左侧 return query(u << 1, l, mid, s, t); else if (s > mid) // 查询区间完全在右侧 return query(u << 1 | 1, mid + 1, r, s, t); else // 左侧部分 + 右侧部分 *注意修改查询区间的边界 return query(u << 1, l, mid, s, mid) + query(u << 1 | 1, mid + 1, r, mid + 1, t); }
板子题:
- P3374 【模板】树状数组 1 - 洛谷
- 单点修改 区间查询
#include <bits/stdc++.h> using namespace std; #define ll long long #define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) const int N = 2e6 + 10; ll n, m, a[N], tr[N]; int lowbit(int x) { return x & -x; } // 单点修改 // a[x] 加上 c void add(int x, int c) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += c; } // 区间查询 // 查询[1, x] 的区间和 ll query(int x) { ll ans = 0; for (int i = x; i; i -= lowbit(i)) ans += tr[i]; return ans; } void solve() { cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], add(i, a[i]); while (m--) { int op, x, y; cin >> op >> x >> y; if (op == 1) add(x, y); else cout << query(y) - query(x - 1) << "\n"; } } int main() { qwq; int T = 1; // cin >> T; while (T--) solve(); return 0; }
#include <bits/stdc++.h> using namespace std; #define ll long long #define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) const int N = 2e6 + 10; ll n, m, a[N], f[N]; // 建树 void build(int u, int l, int r) { if (l == r) { // 递归边界 f[u] = a[l]; return; } int mid = l + r >> 1; build(u << 1, l, mid); // 建左儿子 build(u << 1 | 1, mid + 1, r); f[u] = f[u << 1] + f[u << 1 | 1]; // 父节点区间和 = 左儿子区间和 + 右儿子区间和 } // 单点修改 // 当前修改 u 节点, u 节点所对应区间为[l, r] 要给 a[p] 加上 c void add(int u, int l, int r, int p, int c) { f[u] += c; if (l == r) return; int mid = l + r >> 1; if (p <= mid) // p 在左儿子区间 add(u << 1, l, mid, p, c); else add(u << 1 | 1, mid + 1, r, p, c); } // 区间查询 // 当前为 u 节点, u 节点所对应区间为[l, r] 要查询[s, t]区间和 ll query(int u, int l, int r, int s, int t) { if (l == s && r == t) return f[u]; int mid = l + r >> 1; if (t <= mid) // 查询区间完全在左侧 return query(u << 1, l, mid, s, t); else if (s > mid) // 查询区间完全在右侧 return query(u << 1 | 1, mid + 1, r, s, t); else // 左侧部分 + 右侧部分 *注意修改查询区间的边界 return query(u << 1, l, mid, s, mid) + query(u << 1 | 1, mid + 1, r, mid + 1, t); } void solve() { cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i]; build(1, 1, n); // 1 作为根节点 while (m--) { int op, x, y; cin >> op >> x >> y; if (op == 1) add(1, 1, n, x, y); else cout << query(1, 1, n, x, y) << "\n"; } } int main() { qwq; int T = 1; // cin >> T; while (T--) solve(); return 0; }
#include <bits/stdc++.h> using namespace std; #define ll long long #define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0) const int N = 4e5 + 10; // 四倍数组大小 ll n, m, a[N], f[N], b[N]; // 建树 void build(int u, int l, int r) { if (l == r) { // 递归边界 f[u] = a[l]; return; } int mid = l + r >> 1; build(u << 1, l, mid); // 建左儿子 build(u << 1 | 1, mid + 1, r); f[u] = f[u << 1] + f[u << 1 | 1]; // 父节点区间和 = 左儿子区间和 + 右儿子区间和 } // 区间修改 // 当前为 u 节点, u 节点所对应区间为[l, r] ,要在[s, t]区间每一个数加上c void add(int u, int l, int r, int s, int t, ll c) { if (l == s && r == t) { b[u] += c; // 修改懒标记 不用修改f[u] return; } f[u] += (t - s + 1) * c; // 区间对应的增量 int mid = l + r >> 1; if (t <= mid) // 修改区间完全在左侧 add(u << 1, l, mid, s, t, c); else if (s > mid) // 修改区间完全在右侧 add(u << 1 | 1, mid + 1, r, s, t, c); else add(u << 1, l, mid, s, mid, c), add(u << 1 | 1, mid + 1, r, mid + 1, t, c); } // 区间查询 // 当前为 u 节点, u 节点所对应区间为[l, r] 要查询[s, t]区间和 ll query(int u, int l, int r, int s, int t, ll sum) { sum += b[u]; // sum 记录u 到根节点的标记之和 if (l == s && r == t) return f[u] + sum * (r - l + 1); int mid = l + r >> 1; if (t <= mid) // 查询区间完全在左侧 return query(u << 1, l, mid, s, t, sum); else if (s > mid) // 查询区间完全在右侧 return query(u << 1 | 1, mid + 1, r, s, t, sum); else // 左侧部分 + 右侧部分 *注意修改查询区间的边界 return query(u << 1, l, mid, s, mid, sum) + query(u << 1 | 1, mid + 1, r, mid + 1, t, sum); } void solve() { cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i]; build(1, 1, n); // 1 作为根节点 while (m--) { int op, x, y, k; cin >> op >> x >> y; if (op == 1) { cin >> k; add(1, 1, n, x, y, k); } else { cout << query(1, 1, n, x, y, 0) << "\n"; } } } int main() { qwq; int T = 1; // cin >> T; while (T--) solve(); return 0; }
但