树状数组快速入门题解报告
树状数组快速入门链接:https://www.cnblogs.com/zsxuan/p/17946498
一 基础树状数组,单点修,前缀查 http://oj.daimayuan.top/course/15/problem/634
题意:
给
- 1 x d ,修改
。 - 2 x ,查询
。
题解:
直接对
单点修改的原理是单点加,需要同时修改
view
#include <bits/stdc++.h> typedef long long ll; typedef unsigned long long u64; // ~~ SEG STARST template<class T> struct BIT { std::vector<T> c; int sz, LGN; BIT(){} BIT(int n) { init(n); } void init(int n) { sz = n; // sz 是树状数组的域 LGN = 63 - __builtin_clzll(sz); // LGN 是权值的值域 c.resize(sz + 5); c.assign(sz + 5, 0); } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) s += c[i]; return s; } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) c[i] += s; } int find(T s) { int pos = 0; for (int i = LGN; i >= 0; --i) { if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) { pos += (1 << i); s -= c[pos]; } } return pos; } }; // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin.tie(0); std::ios::sync_with_stdio(false); int n, q; std::cin >> n >> q; std::vector<ll> a(n + 1); BIT<ll> Fe(n); for (int i = 1; i <= n; i++) { std::cin >> a[i]; Fe.modify(i, a[i]); } for (int i = 0; i < q; i++) { int typ = 0; std::cin >> typ; if (typ == 1) { ll x, d; std::cin >> x >> d; Fe.modify(x, d - a[x]); a[x] = d; } else { int x; std::cin >> x; ll ans = Fe.query(x); std::cout << ans << "\n"; } } return 0; }
基础树状数组在加法差分下的拓展
基础树状数组如果不通过
某些情况下具有高效的应用(少推式子)。
二 基于加法差分的树状数组,区间加,区间查
2.1 朴素的加法贡献,区间修改和区间查询 http://oj.daimayuan.top/course/15/problem/635
题意:
给定
支持
- 1 l r d ,令所有的
加上 。 - 2 x ,查询
。
题解:
不难考虑用
因为要
view
#include <bits/stdc++.h> typedef long long ll; typedef unsigned long long u64; // ~~ SEG STARST template<class T> struct BIT_PRO{ std::vector<T> c1, c2; int sz; BIT_PRO(){} BIT_PRO(int n) { init(n); } void init(int n) { sz = n; c1.resize(sz + 5); c2.assign(sz + 5, 0); c1.resize(sz + 5); c2.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c1[i] += s; c2[i] += s * x; } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s += c1[i] * (x + 1); s -= c2[i]; } return s; } void segmodify(int l, int r, T s) { modify(l, s); modify(r + 1, -s); } T segquery(int l, int r) { return query(r) - query(l - 1); } }; // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin.tie(0); std::ios::sync_with_stdio(false); int n, q; std::cin >> n >> q; BIT_PRO<u64> Fe(n); std::vector<u64> a(n + 2); for (int i = 1; i <= n; i++) { a[i] = 0; Fe.segmodify(i, i, a[i]); } for (int i = 0; i < q; i++) { int typ = 0; std::cin >> typ; if (typ == 1) { ll l, r, d; std::cin >> l >> r >> d; Fe.segmodify(l, r, d); } else { int x; std::cin >> x; u64 ans = Fe.query(x); std::cout << ans << "\n"; } } return 0; }
2.2 区间异或,转 01 串翻转 https://codeforces.com/contest/1955/problem/E
题意:
给定一个长度为
题解:
结论: 对于某个
证明: 操作只影响路径,不影响结果。
当这个程序执行完毕后,若
枚举
显然修改是单调的,可以使用类似懒标记的方法做到
但是直接暴力用数据结构
这里使用数据结构暴力修改的解法。
使用线段树几乎一定被卡常数,于是可以使用两个树状数组进行区间加。由于是二进制下的加法:异或,需要重新推导式子。
实际上推导过程差不多:
由
相较于加法公式推导:加法贡献换成异或贡献的同时,前缀和公式中按照
两个树状数组维护即可。
注意一下异或的逻辑:
- 加减法等同异或。
view
#include <bits/stdc++.h> typedef long long ll; const int N = 5005; int a[N]; // ~~ SEG STARST template<class T> struct BIT_PRO{ std::vector<T> c1, c2; int sz, LGN; BIT_PRO(){} BIT_PRO(int n) { init(n); } void init(int n) { sz = n; LGN = 63 - __builtin_clzll(n); c1.resize(sz + 5); c2.assign(sz + 5, 0); c1.resize(sz + 5); c2.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c1[i] ^= s; c2[i] ^= s * (x % 2); } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s ^= c1[i] * ((x + 1) % 2); s ^= c2[i]; } return s; } void segmodify(int l, int r, T s) { modify(l, s); modify(r + 1, s); } T segquery(int l, int r) { return query(r) ^ query(l - 1); } }; // ~~ SEG END void solve() { int n; std::cin >> n; std::string s; std::cin >> s; for (int i = 1; i <= n; i++) { a[i] = s[i - 1] - '0'; } auto check = [&](int k) -> bool { BIT_PRO<ll> Fe(n); for (int i = 1; i <= n; i++) { Fe.segmodify(i, i, a[i]); } for (int i = 1; i + k - 1 <= n; i++) { // !!! k != 0 if (Fe.segquery(i, i) == 0) { Fe.segmodify(i, i + k - 1, 1); } } int ok = 1; for (int i = 1; i <= n; i++) { ok &= Fe.segquery(i, i) == 1; } return ok; }; for (int i = n; i >= 1; --i) { if (check(i)) { std::cout << i << "\n"; return; } } } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin.tie(0); std::ios::sync_with_stdio(false); int _ = 1; std::cin >> _; while (_--) { solve(); } return 0; }
2.3 高阶加法差分,无聊的简单推式子 https://www.luogu.com.cn/problem/P1438
题意:
给一个长度为
执行
- 1 l r K D ,对区间
执行 。 - 2 x ,查询
。
题解:
设差分数组
于是考虑只需要对
第一部分。对原数组
第二部分。对
思考二阶差分的一些性质:
让
只需
经验算是正确的。
查询
view
#include <bits/stdc++.h> typedef long long ll; typedef unsigned long long u64; // ~~ SEG STARST template<class T> struct BIT_PRO1{ std::vector<T> c1, c2; int sz; BIT_PRO1(){} BIT_PRO1(int n) { init(n); } void init(int n) { sz = n; c1.resize(sz + 5); c1.assign(sz + 5, 0); c2.resize(sz + 5); c2.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c1[i] += s; c2[i] += s * x; } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s += c1[i] * (x + 1); s -= c2[i]; } return s; } void segmodify(int l, int r, T s) { modify(l, s); modify(r + 1, -s); } T segquery(int l, int r) { return query(r) - query(l - 1); } }; template<class T> struct BIT_PRO2{ std::vector<T> c1, c2, c3; int sz; BIT_PRO2(){} BIT_PRO2(int n) { init(n); } void init(int n) { sz = n; c1.resize(sz + 5); c1.assign(sz + 5, 0); c2.resize(sz + 5); c2.assign(sz + 5, 0); c3.resize(sz + 5); c3.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c1[i] += s; c2[i] += s * x; c3[i] += s * (x * x - x * 3) / 2; } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s += c1[i] * (x + 1) * (x + 2) / 2; s -= c2[i] * x; s += c3[i]; } return s; } void segmodify(int l, int r, T s) { modify(l, s); modify(r + 1, -s); } T segquery(int l, int r) { return query(r) - query(l - 1); } }; // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin.tie(0); std::ios::sync_with_stdio(false); int n, q; std::cin >> n >> q; std::vector<ll> a(n + 2), d(n + 2); BIT_PRO1<ll> P(n); BIT_PRO2<ll> Q(n); for (int i = 1; i <= n; i++) { std::cin >> a[i]; P.segmodify(i, i, a[i]); Q.segmodify(i, i, 0); } for (int i = 0; i < q; i++) { int typ = 0; std::cin >> typ; if (typ == 1) { ll l, r, K, D; std::cin >> l >> r >> K >> D; P.segmodify(l, r, K); Q.segmodify(l + 1, r, D); P.segmodify(r + 1, n, -D * (r - (l + 1) + 1)); } else { int x; std::cin >> x; ll ans = P.segquery(x, x) + Q.segquery(x, x); std::cout << ans << "\n"; } } return 0; }
三 Fenwick Tree,倍增
3.1 Fenwick Tree 倍增 http://oj.daimayuan.top/course/15/problem/636
题意:
给
支持
- 1 x d ,修改
。 - 2 s ,查询最大的
满足 。
题解:
比较显然的,
注意输入
所以可以直接写代码?
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT { std::vector<T> c; int sz, LGN; BIT(){} BIT(int n) { init(n); } void init(int n) { sz = n + 1; // sz 是树状数组的域 LGN = 63 - __builtin_clzll(sz); // LGN 是权值的值域 c.resize(sz + 5); c.assign(sz + 5, 0); } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) s += c[i]; return s; } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) c[i] += s; } int find(T s) { int pos = 0; for (int i = LGN; i >= 0; --i) { if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) { pos += (1 << i); s -= c[pos]; } } return pos; } }; BIT<ll> C; // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n, q; std::cin >> n >> q; C.init(n); std::vector<ll> a(n + 1); for (int i = 1; i <= n; i++) { std::cin >> a[i]; C.modify(i, a[i]); // std::cout << a[i] << " \n"[i == n]; } for (int i = 0; i < q; i++) { int typ = 0; std::cin >> typ; if (typ == 1) { int x, d; std::cin >> x >> d; C.modify(x, d - a[x]); a[x] = d; // for (int i = 1; i <= n; i++) { // std::cout << C.query(i) - C.query(i - 1) << " \n"[i == n]; // } } else { ll s; std::cin >> s; int T = C.find(s); if (T > n) T = n; std::cout << T << "\n"; } } return 0; }
3.2 Fenwick Tree 第 K 小 https://www.luogu.com.cn/problem/P1168
观察倍增求
动态全局第
动态全局第
题意:
给定
题解:
逐渐加入新的元素求解答案,似乎很像是
离散化:
值域很大,但一维点值离散化后值域个数等同于元素个数
- 离散化数组
查出来的 从 开始。 - px 往右偏移一位可用 Fenwick Tree 维护。
- num[anspx - 1] 即原数组的值。
维护:
考虑
可以使用对顶堆。
也可直接用 Fenwick Tree ,将点权维护成
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT { std::vector<T> c; int sz, LGN; BIT(){} BIT(int n) { init(n); } void init(int n) { sz = n + 5; // sz 是树状数组的域 LGN = 63 - __builtin_clzll(sz); // LGN 是权值的值域 c.resize(sz); c.assign(sz, 0); } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) s += c[i]; return s; } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) c[i] += s; } int find(T s) { int pos = 0; for (int i = LGN; i >= 0; --i) { if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) { pos += (1 << i); s -= c[pos]; } } return pos; } }; // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n; std::cin >> n; std::vector<int> a(n + 1); std::vector<int> numx; for (int i = 1; i <= n; i++) { std::cin >> a[i]; numx.push_back(a[i]); } std::sort(numx.begin(), numx.end()); numx.erase(std::unique(numx.begin(), numx.end()), numx.end()); BIT<ll> Fe(n); for (int i = 1; i <= n; i++) { int px = std::lower_bound(numx.begin(), numx.end(), a[i]) - numx.begin() + 1; Fe.modify(px, 1); if (i & 1) { ll anspx = Fe.find((i + 1) / 2 - 1) + 1; std::cout << numx[anspx - 1] << "\n"; } } return 0; }
四 Fenwick Tree ,二维前缀偏序,二维加法偏序
4.1 Fenwick Tree,二维前缀偏序,逆序对 https://www.luogu.com.cn/problem/P1908
题意:
给
题解:
逆序对是个经典的可以使用归并排序解决的问题。也是一类二维前缀偏序问题。
但是我们追求优秀、无脑、简单、通用、熟练。所以直接使用 Fenwick Tree 。
值域很大,离散化成点值个数。注意调整偏移量。
逆序对:顾名思义:
显然有二维前缀(后缀)偏序:
从右往左扫,计算
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT{ std::vector<T> c; int sz, LGN; BIT(){} BIT(int n){ init(n); } void init(int n) { sz = n + 1; LGN = 31 - __builtin_clz(sz); c.resize(sz + 5); c.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c[i] += s; } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s += c[i]; } return s; } int find(T s) { int pos = 0; for (int i = LGN; i >= 0; --i) { if (pos + (1 << i) <= sz && c[pos + (1 << i)] <= s) { pos += (1 << i); s -= c[pos]; } } return pos; } }; template<class T> inline bool read(T &res) { char c = getchar(); int sgn; if (c == EOF) return 0; if (c == '-') sgn = -1, res = 0; else sgn = 1, res = c - '0'; while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0'); res *= sgn; return 1; } // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n; read<int>(n); BIT<ll> Fe(n); std::vector<ll> a(n + 1); std::vector<ll> numx; for (int i = 1; i <= n; i++) { read<ll>(a[i]); numx.push_back(a[i]); } std::sort(numx.begin(), numx.end()); numx.erase(std::unique(numx.begin(), numx.end()), numx.end()); ll ans = 0; for (int i = n; i; --i) { int px = std::lower_bound(numx.begin(), numx.end(), a[i]) - numx.begin() + 1; ans += Fe.query(px - 1); Fe.modify(px, 1); } std::cout << ans << "\n"; return 0; }
4.2 Fenwick Tree,二维前缀偏序,LIS https://acm.hdu.edu.cn/showproblem.php?pid=5256
题意:
题解:
不难想到,在给定偏序下,每个数在偏序上的相对位置固定。
不难考虑,我们找到一个符合偏序的极大子序列,即最长上升子序列。将其他数全部并入这个序列便能实现整个序列的偏序。
如果给定的是非全偏序
根据整数的离散性
如果给定的是全偏序
这是一个经典结论题。
这里我们构造一个新序列和原序列的解集双射。
核心在于
容易证明执行这个操作后,每个新的非降子序列和每个旧的上升子序列双射。
证明:
充分性:若一个序列是非降子序列,则
必要性:若一个序列是上升子序列,则
求解最长非降子序列长度
考虑求解最长上升子序列
显然有二维前缀偏序:
而最长非降子序列只需把限制
具体的实现,需要让
离散化过的数是不能改的!!!注意先修改再离散化。
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT{ std::vector<T> c; int sz, LGN; BIT(){} BIT(int n){ init(n); } void init(int n) { sz = n + 1; LGN = 31 - __builtin_clz(sz); c.resize(sz + 5); c.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c[i] = std::max(c[i], s); } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s = std::max(s, c[i]); } return s; } }; template<class T> inline bool read(T &res) { char c = getchar(); int sgn; if (c == EOF) return 0; if (c == '-') sgn = -1, res = 0; else sgn = 1, res = c - '0'; while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0'); res *= sgn; return 1; } template<class T> inline void write(T x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int testcase; read<int>(testcase); for (int test = 1; test <= testcase; test++) { printf("Case #%d:\n", test); int n; read<int>(n); BIT<ll> Fe(n + 1); std::vector<ll> a(n + 1); std::vector<ll> numy; for (int i = 1; i <= n; i++) { read<ll>(a[i]); a[i] += n + 1 - i; numy.push_back(a[i]); } std::sort(numy.begin(), numy.end()); numy.erase(std::unique(numy.begin(), numy.end()), numy.end()); std::vector<ll> dp(n + 1); dp[0] = 0; ll ans = 0; for (int i = 1; i <= n; i++) { int py = std::lower_bound(numy.begin(), numy.end(), a[i]) - numy.begin() + 1; dp[i] = Fe.query(py) + 1; ans = std::max(ans, dp[i]); Fe.modify(py, dp[i]); } write<ll>(n - ans); puts(""); } return 0; }
4.3 Fenwick Tree,二维加法偏序,最大和上升子序列 http://oj.daimayuan.top/course/15/problem/780
题意:
给一个长度为
也就是说,找到
题解:
最优结果,考虑暴力 DP 怎么做。设以
显然的二维前缀偏序:
于是用 Fenwick Tree 维护 DP 。
如果
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT{ std::vector<T> c; int sz, LGN; BIT(){} BIT(int n){ init(n); } void init(int n) { sz = n + 1; LGN = 31 - __builtin_clz(sz); c.resize(sz + 5); c.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c[i] = std::max(c[i], s); } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s = std::max(s, c[i]); } return s; } }; template<class T> inline bool read(T &res) { char c = getchar(); int sgn; if (c == EOF) return 0; if (c == '-') sgn = -1, res = 0; else sgn = 1, res = c - '0'; while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0'); res *= sgn; return 1; } template<class T> inline void write(T x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n; read<int>(n); std::vector<ll> a(n + 1); ll K = 0; for (int i = 1; i <= n; i++) { read<ll>(a[i]); K = std::max(K, a[i]); } BIT<ll> Fe(K); std::vector<ll> dp(n + 1); ll ans = 0; for (int i = 1; i <= n; i++) { dp[i] = Fe.query(a[i] - 1) + a[i]; ans = std::max(dp[i], ans); Fe.modify(a[i], dp[i]); } write<ll>(ans); puts(""); return 0; }
五 狭义扫描线
需要二维偏序都能算是扫描线,但扫描线不是二维偏序。
狭义扫描线是:需要离线的二维数点问题的解决方案
二维加法偏序一定是二维数点,二维数点不一定是二维偏序。(如矩形面积并不是二维偏序,但是二维数点)
5.1 Fenwick Tree,二维加法偏序,离散化,扫描线,容斥 http://oj.daimayuan.top/course/15/problem/686
题解:
- 将操作和询问离线并排序。
- 扫描线直接扫描二维问题时,通常顺序扫描
轴方便。于是点的竖直优先级最高。 - 次优询问和修改的顺序。点的“水平优先级、所属询问的
” 顺序不重要。
修改直接插入一个点,不妨编号成第
询问矩形
询问优先级:
- 四个点排序优先级是前两个减去后两个。
- 询问优先级低于修改。
扫描线只需要离散化另一维:由于事件被排序成了从
遇到修改事件时,进行修改。优先级高于询问事件。
遇到询问事件时,调整事件对应询问的答案。
询问时的二维偏序是:
修改时往 Fenwick Tree 插入一个点。
一个开闭区间离散化的技术处理:
正常情况下在离散数组中直接 lower_bound[x] 就能找到 x 离散出的值。
但是容斥矩形的四个点存在开闭区间的处理,只看
对
这时候 uper_bound[x] - 1 才能保证找到的开闭区间的端点都合法。
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT{ std::vector<T> c; int sz, LGN; BIT(){} BIT(int n){ init(n); } void init(int n) { sz = n + 1; LGN = 31 - __builtin_clz(sz); c.resize(sz + 5); c.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c[i] += s; } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s += c[i]; } return s; } }; template<class T> inline bool read(T &res) { char c = getchar(); int sgn; if (c == EOF) return 0; if (c == '-') sgn = -1, res = 0; else sgn = 1, res = c - '0'; while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0'); res *= sgn; return 1; } template<class T> inline void write(T x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n, q; read<int>(n); read<int>(q); std::vector<std::array<int, 4> > event; // y, priority, x, id std::vector<int> vx; for (int i = 1; i <= n; i++) { int x, y; read<int>(x); read<int>(y); vx.push_back(x); event.push_back({y, 0, x, 0}); } for (int i = 1; i <= q; i++) { int x1, x2; read<int>(x1); read<int>(x2); int y1, y2; read<int>(y1); read<int>(y2); event.push_back({y2, 1, x2, i}); event.push_back({y1 - 1, 1, x1 - 1, i}); event.push_back({y2, 2, x1 - 1, i}); event.push_back({y1 - 1, 2, x2, i}); } std::sort(event.begin(), event.end()); std::sort(vx.begin(), vx.end()); vx.erase(std::unique(vx.begin(), vx.end()), vx.end()); BIT<int> Fe((int)vx.size()); std::vector<ll> ans(q + 1); for (auto evt : event) { if (evt[1] == 0) { int px = std::lower_bound(vx.begin(), vx.end(), evt[2]) - vx.begin() + 1; Fe.modify(px, 1); } else { int px = std::upper_bound(vx.begin(), vx.end(), evt[2]) - vx.begin(); int tmp = Fe.query(px); if (evt[1] == 1) ans[evt[3]] += tmp; else ans[evt[3]] -= tmp; } } for (int i = 1; i <= q; i++) { write<ll>(ans[i]); puts(""); } return 0; }
5.2 Fenwick Tree,二维前缀偏序/二维加法偏序,询问离线,扫描线?区间不同数之和 http://oj.daimayuan.top/course/15/problem/687
题意:
给
题解:
需要一个技术处理:
技术处理:
具体需要处理一个 last[a[i]] = i, pre[i] = last[a[i]] 。
存在两种方法:
方法 1:
狭义扫描线
需要离线的,非全序的,二维加法偏序。都等价于离线的二维数点,都能用狭义扫描线处理。
全序显然比非全序更好解决,直接广义扫描线就能处理。
观察任意区间
这是个经典的处理,类似的有:https://www.luogu.com.cn/problem/P8773(选数异或)
即是一个二维偏序:
这个区间只要用两个矩形就能容斥。
event 记录 x,y,操作优先级,操作 id 。确保 y 为第一优先级(扫描轴),操作优先级为第二优先级。其他优先级不重要。
- 修改:for i = 1 to n,
。 - 询问:for i = 1 to q,
。
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT{ std::vector<T> c; int sz, LGN; BIT(){} BIT(int n){ init(n); } void init(int n) { sz = n + 1; LGN = 31 - __builtin_clz(sz); c.resize(sz + 5); c.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c[i] += s; } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s += c[i]; } return s; } }; template<class T> inline bool read(T &res) { char c = getchar(); int sgn; if (c == EOF) return 0; if (c == '-') sgn = -1, res = 0; else sgn = 1, res = c - '0'; while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0'); res *= sgn; return 1; } template<class T> inline void write(T x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } // ~~ SEG END const int N = 2E5 + 10; int last[N]; signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n, q; read<int>(n); read<int>(q); BIT<ll> Fe(n + 1); std::vector<int> a(n + 1); std::vector<std::array<int, 4> > event; // y, priority, x, id std::vector<int> pre(n + 1); for (int i = 1; i <= n; i++) { read<int>(a[i]); pre[i] = last[a[i]]; event.push_back({pre[i], 0, i, 0}); last[a[i]] = i; } for (int i = 1; i <= q; i++) { int l, r; read<int>(l); read<int>(r); event.push_back({l - 1, 1, r, i}); event.push_back({l - 1, 2, l - 1, i}); } std::sort(event.begin(), event.end()); std::vector<ll> ans(q + 1); for (auto evt : event) { if (evt[1] == 0) { // modify 不涉及 0 Fe.modify(evt[2], a[evt[2]]); } else { if (evt[1] == 1) ans[evt[3]] += Fe.query(evt[2]); else ans[evt[3]] -= Fe.query(evt[2]); } } for (int i = 1; i <= q; i++) { write<ll>(ans[i]); puts(""); } for (int i = 1; i <= n; i++) { last[a[i]] = 0; } return 0; }
方法 2:
维护每个点到当前的更新点的答案。
for r = 1 to n 作为更新点。
观察任意区间
考虑更新:
观察到任意某个区间
显然
利用树状数组区间修改,单点查询。
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT{ std::vector<T> c; int sz, LGN; BIT(){} BIT(int n){ init(n); } void init(int n) { sz = n + 1; LGN = 31 - __builtin_clz(sz); c.resize(sz + 5); c.assign(sz + 5, 0); } void modify(int x, T s) { assert(x > 0); for (int i = x; i <= sz; i += i & -i) { c[i] += s; } } T query(int x) { assert(x <= sz); T s = 0; for (int i = x; i; i -= i & -i) { s += c[i]; } return s; } }; template<class T> inline bool read(T &res) { char c = getchar(); int sgn; if (c == EOF) return 0; if (c == '-') sgn = -1, res = 0; else sgn = 1, res = c - '0'; while (c = getchar(), c >= '0' && c <= '9') res = res * 10 + (c - '0'); res *= sgn; return 1; } template<class T> inline void write(T x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } // ~~ SEG END const int N = 2E5 + 10; int last[N]; signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n, q; read<int>(n); read<int>(q); BIT<ll> Fe(n + 1); std::vector<int> a(n + 1); for (int i = 1; i <= n; i++) { read<int>(a[i]); } std::vector<int> l(q + 1), r(q + 1); std::vector<std::vector<std::array<int, 2> > > belong(n + 1); for (int i = 1; i <= q; i++) { int l, r; read<int>(l); read<int>(r); belong[r].push_back({l, i}); } std::vector<ll> ans(q + 1); int pre = 0; for (int r = 1; r <= n; r++) { pre = last[a[r]]; Fe.modify(pre + 1, a[r]); Fe.modify(r + 1, -a[r]); last[a[r]] = r; for (auto blg : belong[r]) { ans[blg[1]] = Fe.query(blg[0]); } } for (int i = 1; i <= q; i++) { write<ll>(ans[i]); puts(""); } for (int i = 1; i <= n; i++) { last[a[i]] = 0; } return 0; }
六 高维树状数组
每一维单次操作的时间复杂度
6.1 二维单点修改,区间查询 http://oj.daimayuan.top/course/15/problem/637
题意:
给
支持
- 1 x y d ,修改
。 - 2 x y ,查询
。
题解:
就是很朴素的二维区间上维护树状数组,每一维都基于 lowbit 维护。
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT_2D{ std::vector<std::vector<T> > c; int szx, szy; BIT_2D(){} BIT_2D(int n, int m){ init(n, m); } void init(int n, int m) { szx = n + 1; szy = m + 1; c.resize(szx + 5); for (auto &u : c) u.resize(szy + 5); for (auto &u : c) u.assign(szy + 5, 0); } void modify(int x, int y, T s) { assert(x > 0 && y > 0); for (int i = x; i <= szx; i += i & -i) { for (int j = y; j <= szy; j += j & -j) { c[i][j] += s; } } } T query(int x, int y) { assert(x <= szx && y <= szy); T s = 0; for (int i = x; i; i -= i & -i) { for (int j = y; j; j -= j & -j) { s += c[i][j]; } } return s; } }; // ~~ SEG END signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n, m, q; std::cin >> n >> m >> q; BIT_2D<ll> Fe(n, m); std::vector<std::vector<ll> > g(n + 1, std::vector<ll>(m + 1)); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { std::cin >> g[i][j]; Fe.modify(i, j, g[i][j]); } } for (int i = 0; i < q; i++) { int typ = 0; std::cin >> typ; if (typ == 1) { ll x, y, d; std::cin >> x >> y >> d; assert(x <= n && y <= m); Fe.modify(x, y, d - g[x][y]); g[x][y] = d; } else { int x, y; std::cin >> x >> y; ll ans = Fe.query(x, y); std::cout << ans << "\n"; } } return 0; }
6.2 二维区间加,区间查询 https://loj.ac/p/135
题意:
给一个大小为
操作有两类:
- 1 a b c d x ,表示将右上角
到左下角 的子矩阵加上 。 - 2 a b c d ,表示询问右上角
到左下角 的子矩阵数字和。
令其是笛卡尔坐标系。给的是零矩阵,不需要关注端点。
否则笛卡尔坐标系的前缀是左下角,不妨
题解:
微笑着推式子吧:
建立四个树状数组
询问时分别补上
修改时分别补上
view
#include <bits/stdc++.h> typedef long long ll; // ~~ SEG STARST template<class T> struct BIT_2D_PRO { std::vector<std::vector<T> > c1, c2, c3, c4; int szx, szy; BIT_2D_PRO(){} BIT_2D_PRO(int n, int m) { init(n, m); } void init(int n, int m) { szx = n; szy = m; c1.resize(szx + 5); for (auto &u : c1) u.resize(szy + 5); for (auto &u : c1) u.assign(szy + 5, 0); c2.resize(szx + 5); for (auto &u : c2) u.resize(szy + 5); for (auto &u : c2) u.assign(szy + 5, 0); c3.resize(szx + 5); for (auto &u : c3) u.resize(szy + 5); for (auto &u : c3) u.assign(szy + 5, 0); c4.resize(szx + 5); for (auto &u : c4) u.resize(szy + 5); for (auto &u : c4) u.assign(szy + 5, 0); } void modify(int x, int y, T s) { assert(x > 0 && y > 0); for (int i = x; i <= szx; i += i & -i) { for (int j = y; j <= szy; j += j & -j) { c1[i][j] += s; c2[i][j] += s * y; c3[i][j] += s * x; c4[i][j] += s * x * y; } } } T query(int x, int y) { assert(x <= szx && y <= szy); T s = 0; for (int i = x; i; i -= i & -i) { for (int j = y; j; j -= j & -j) { s += c1[i][j] * (x + 1) * (y + 1); s -= c2[i][j] * (x + 1); s -= c3[i][j] * (y + 1); s += c4[i][j]; } } return s; } void recmodify(int a, int b, int c, int d, ll s) { modify(a, b, s); modify(a, d + 1, -s); modify(c + 1, b, -s); modify(c + 1, d + 1, s); } T recquery(int a, int b, int c, int d) { return query(c, d) - query(a - 1, d) - query(c, b - 1) + query(a - 1, b - 1); } }; // ~~ SEG END // 注意启用后关闭 cin cout 缓冲 优化且对 c++ oi流 停用 template<class T> inline bool read(T &ret) { char c; int sgn; if (c = getchar(), c == EOF) return 0; // 先判结尾 while (c != '-' && (c < '0' || c > '9')) c = getchar(); //再判是一个数字 sgn = (c == '-') ? -1 : 1; ret = (c == '-') ? 0 : (c - '0'); while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0'); //直到读完 ret *= sgn; return 1; } template<class T> inline void write(T x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n, m; read<int>(n); read<int>(m); BIT_2D_PRO<ll> Fe(n, m); std::vector<std::vector<ll> > g(n + 2, std::vector<ll>(m + 2)); std::vector<std::vector<ll> > d(n + 2, std::vector<ll>(m + 2)); // for (int i = 1; i <= n; i++) { // for (int j = 1; j <= m; j++) { // g[i][j] = 0; // d[i][j] = g[i][j] - g[i - 1][j] - g[i][j - 1] + g[i - 1][j - 1]; // Fe.recmodify(i, j, i, j, d[i][j]); // } // } int typ; while (read<int>(typ)) { if (typ == 1) { int a, b, c, d, x; read<int>(a); read<int>(b); read<int>(c); read<int>(d); read<int>(x); Fe.recmodify(a, b, c, d, x); } else { int a, b, c, d; read<int>(a); read<int>(b); read<int>(c); read<int>(d); ll ans = Fe.recquery(a, b, c, d); write<ll>(ans); puts(""); } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】