树状数组快速入门题解报告
树状数组快速入门链接:https://www.cnblogs.com/zsxuan/p/17946498
一 基础树状数组,单点修,前缀查 http://oj.daimayuan.top/course/15/problem/634
题意:
给 \(n\) 个数 \(a_1, a_2, a_3, \cdots, a_n\) 。
\(q\) 个操作:
- 1 x d ,修改 \(a_x = d\) 。
- 2 x ,查询 \(\sum_{i = 1}^{x} a_x\) 。
\(1 \neq n, q \leq 2 \times 2 \times 10^{5}, 1 \leq a_i, d \leq 10^{9}\), \(1 \leq x \leq n\) 。
题解:
直接对 \(1 \sim n\) 定义域建出树状数组。朴素的单点修改和前缀查询即可。
单点修改的原理是单点加,需要同时修改 \(a\) 和 树状数组。
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;
}
基础树状数组在加法差分下的拓展
基础树状数组如果不通过 \(a\) 维护 \(s\) ,而是通过 \(d\) 维护 \(a\) 。在加法运算下显然可以区间修改,单点查询。
某些情况下具有高效的应用(少推式子)。
二 基于加法差分的树状数组,区间加,区间查
2.1 朴素的加法贡献,区间修改和区间查询 http://oj.daimayuan.top/course/15/problem/635
题意:
给定 \(n\) 个数,一开始都是 \(0\) 。
支持 \(q\) 个操作:
- 1 l r d ,令所有的 \(a_i(l \leq i \leq r)\) 加上 \(d\) 。
- 2 x ,查询 \(\sum_{i = 1}^{x} a_i \bmod 2^{64}\) 。
\(1 \leq n, q \leq 2 \times 10^{5}, 1 \leq d \leq 10^{9}, 1 \leq l \leq r \leq n\) 。
题解:
不难考虑用 \(d\) 表示 \(s\) ,有 \(s_x = (x + 1) \sum_{i = 1}^{x} d_i - \sum_{x = 1}^{x} i d_i\) 。
因为要 \(\bmod 2^{64}\) ,于是权值使用 \(u64\) 类型存储即可。
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
题意:
给定一个长度为 \(n\) 的 \(01\) 字符串 \(s\) 。选择一个 \(k, (1 \leq k \leq n)\) ,可以对 \(s\) 长度为 \(k\) 的任意子段翻转任意次。询问最大的 \(k\) ,使 \(s\) 最终可以成为全 \(1\) 串。
\(1 \leq n \leq 5000\) 。
题解:
结论: 对于某个 \(k\) ,只需保证 \(s\) 前缀是全 \(1\) 串,对于第一个是 \(0\) 的位置 \(p\ s.t.\ p + k - 1 \leq n\) ,只需翻转 \([p, p + k - 1]\) 。
证明: 操作只影响路径,不影响结果。\(\square\)
当这个程序执行完毕后,若 \(s\) 是全 \(1\) 串,则 \(k\) 合法。否则不合法。
枚举 \(k \in [1, n]\) \(check\) 出最大 \(k\) 即可。
显然修改是单调的,可以使用类似懒标记的方法做到 \(O(n)\) 修改 。
但是直接暴力用数据结构 \(O(n \log n)\) 区间加也是满足复杂度的。
这里使用数据结构暴力修改的解法。
\(T(n ^ 2 \log n) = 2.5 \times 10^{6} \times 30 = 7.5 \times 10^{8}\) ,时限 \(3\ S\) 。
使用线段树几乎一定被卡常数,于是可以使用两个树状数组进行区间加。由于是二进制下的加法:异或,需要重新推导式子。
实际上推导过程差不多:
由
相较于加法公式推导:加法贡献换成异或贡献的同时,前缀和公式中按照 \(\mod 2\) 意义下计算 \(x + 1\) 和 \(i\) 的乘法贡献。
两个树状数组维护即可。
注意一下异或的逻辑:
- 加减法等同异或。
- \(\bigoplus_{i = 1}^{n} x = x \oplus (n \bmod 2)\)
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
题意:
给一个长度为 \(n\) 的数组 \(a_1, a_2, a_3, \cdots, a_n\) 。
执行 \(m\) 个操作:
- 1 l r K D ,对区间 \([l, r]\) 执行 \(a_l += K, a_{l + 1} += K + D, a_{l + 2} += K + 2D, \cdots, a_{r} += K + (r - l)D\) 。
- 2 x ,查询 \(a_x\) 。
\(1 \leq n, m \leq 10^{5}, 1 \leq a_i, k, d \leq 200, 1 \leq x \leq n\) 。
题解:
设差分数组 \(d\) ,数组 \(a\) ,前缀和数组 \(s\) ,二阶前缀和数组 \(w\) 。
\(d_{l} += y, d_{r + 1} -= y\) 的影响为:\(s_{l \sim r} += y\) ,\(w_{i \in [l, r]} += iy, w_{r + 1 \cdots} += (r + l - 1)y\)。
于是考虑只需要对 \([l, r]\) 区间加 \(K\) ,\([l + 1, r]\) 等差加 \(D\) ,\([r + 1, n]\) 区间减 \((r - (l + 1) - 1) D\) 。
第一部分。对原数组 \(a\) 建立一组树状数组 \(P\) 以维护它的 \(s\) 数组。执行 \([l, r]\) 的 \(k, d\) 修改时,对 \(P\) 进行 \([l, r]\) 区间均值加。
\(P\) 可以通过两个树状数组 \(P_1, P_2\) 维护,不难推出 \(s_x = (x + 1) \sum_{i = 1}^{x} d_i - \sum_{i = 1}^{x} i \times d_i\) 。
第二部分。对 \(0\) 数组建立一组树状数组 \(Q\) 以维护 \(w\) 数组。对 \(Q\) 进行 \([l + 1, r]\) 区间等差加。
思考二阶差分的一些性质:
\(d_x = \Delta(\Delta s_x) = \Delta s_{x} - \Delta s_{x - 1} = s_{x} - 2 s_{x - 1} + s_{x - 2}\) 。
\(d_x + k \Rightarrow s_{x} + k, s_{x + 1} + 2k, s_{x + 2} + 3k, \cdots\) 。
让 \(w_{l \sim r} + k\) 。
只需 \(d_{l} + k, d_{r + 1} - k\) ,得到 \(w_{l \sim r} + k, w_{r + 1 \cdots} + (r - l + 1)k\) 。后一部分再用某组树状数组将后缀减去 \((r - l + 1)k\) 即可,不妨用 \(P\) 。
\(Q\) ,考虑推式子,使得 \(w\) 被 \(d\) 表示:
经验算是正确的。
查询 \(P_x + Q_x\) 即 \(1 \sim 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
题意:
给 \(n\) 个数 \(a_1, a_2, a_3, \cdots, a_n\) 。
支持 \(q\) 个操作:
- 1 x d ,修改 \(a_x = d\) 。
- 2 s ,查询最大的 \(T(0 \leq T \leq n)\) 满足 \(\sum_{i = 1}^{T} a_i \leq s\) 。
\(1 \leq n, q \leq 2 \times 10^{5}, 1 \leq a_i \leq 10^{9}, 1 \leq x \leq n, 1 \leq d \leq 10^{9}, 1 \leq s \leq 10^{14}\) 。
题解:
比较显然的,\(T\) 是树状数组上通过一个快速到达 \(s\) ,倍增得到。
注意输入 \(s\) 可能大于等于集合的 max ,特判一下倍增结果是否越界。
所以可以直接写代码?
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
观察倍增求 \(max\ Y\ s.t.\ sum_{i = 1}^{T} a_i \leq s\) ,实际上不仅满足 \(\leq s\) ,还包含了一段 \(0\) 的后缀使得 \(\sum_{i = 1}^{T + 1} a_i = s + 1\) 。于是第 \(k\) 小可以是 \(find(s - 1) + 1\) 。
动态全局第 \(k\) 小问题可以用堆顶堆解决,也可以直接用 Fenwicik Tree 解决,两者并没有复杂度差距。
动态全局第 \(k\) 小的经典应用是动态中位数。
题意:
给定 \(n\) 个非负整数 \(a_1, a_2, a_3, \cdots, a_n\) ,对前奇数项求中位数。
\(1 \leq n \leq 10^{5}\) ,\(1 \leq 10^{9}\) 。
题解:
逐渐加入新的元素求解答案,似乎很像是 \(dp\) ,然而不是。首先 \(dp\) 求解的是最优答案,其次这题无法转移。实际上这是个经典的动态第 \(k\) 小问题。
离散化:
值域很大,但一维点值离散化后值域个数等同于元素个数 \(\times 1\) 。
- 离散化数组 \(numx\) 查出来的 \(rk\) 从 \(0\) 开始。
- px 往右偏移一位可用 Fenwick Tree 维护。
- num[anspx - 1] 即原数组的值。
维护:
考虑 \(1 \sim n\ s.t.\ n - 2 \in odd\) 位求出了中位数,加入后两个数的情况。
可以使用对顶堆。
也可直接用 Fenwick Tree ,将点权维护成 \(1\) 的加法贡献,于是可以维护住动态的全局第 \(\frac{n + 1}{2}\) 小。
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
题意:
给 \(n\) 个数 \(a_1, a_2, a_c, \cdots, a_n\) 。输出这个序列的逆序对个数。
\(1 \leq n \leq 5 \times 10^{5}, 1 \leq 10^{9}\) 。
题解:
逆序对是个经典的可以使用归并排序解决的问题。也是一类二维前缀偏序问题。
但是我们追求优秀、无脑、简单、通用、熟练。所以直接使用 Fenwick Tree 。
值域很大,离散化成点值个数。注意调整偏移量。
逆序对:顾名思义:\(\forall x, a_y < a_x\ s.t.\ y > x\) 的数量。
显然有二维前缀(后缀)偏序:
从右往左扫,计算 \(cnt\ H(a_j)\ s.t. H(a_j) < H(a_i)\) 的贡献,然后将 \(H(a_i)\) 加入偏序。\(H(x)\) 为 \(x\) 离散化后的数。
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
题意:
\(n\) 个数的序列为 \(a_1, a_2, a_3, \cdots, a_n\) 。询问最少修改多少个数可使得序列严格递增。
\(1 \leq n \leq 10^{5}, 1 \leq a_i \leq 10^{6}\) 。
题解:
不难想到,在给定偏序下,每个数在偏序上的相对位置固定。
不难考虑,我们找到一个符合偏序的极大子序列,即最长上升子序列。将其他数全部并入这个序列便能实现整个序列的偏序。
如果给定的是非全偏序 \((G, \leq)\) ,上述这就是答案。
根据整数的离散性 \(|x - y| \geq 1\) ,如果相邻两个数是 \(1\) ,我们没法在中间插入一个数。
如果给定的是全偏序 \((G, <)\) 某个数不一定可以并入选择的极大序列。
这是一个经典结论题。
这里我们构造一个新序列和原序列的解集双射。
核心在于 \(\forall x \in [1, n], a_x = a_x - x\) 。
容易证明执行这个操作后,每个新的非降子序列和每个旧的上升子序列双射。
证明:
充分性:若一个序列是非降子序列,则 \(a_x = a_x + x\) 后,得到的序列是上升子序列。
必要性:若一个序列是上升子序列,则 \(a_x = a_x - x\) 后,得到的序列是非降子序列。
\(\square\)
求解最长非降子序列长度 \(len\) ,\(n - len\) 即最少要修改的个数。
考虑求解最长上升子序列 \(LIS\) ,典型的有 DP \(dp_i = dp_j + 1\ s.t. j < i, a_j < a_i, max(dp_{j})\) 。
显然有二维前缀偏序:
而最长非降子序列只需把限制 \(a_j < a_i\) 换成 \(a_j \leq a_i\) 。
具体的实现,需要让 \(a_x = a_x + n + 1\) ,再 \(a_x = a_x - x\) ,避开了非正整数。然后再离散化 \(a\) 数组。
离散化过的数是不能改的!!!注意先修改再离散化。
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
题意:
给一个长度为 \(n\) 的数组 \(a_1, a_2, a_3, \cdots, a_n\) ,问其中的和最大的上升子序列。
也就是说,找到 \(1 \leq p_1 < p_2 < p_3 < \cdots < p_m \leq n\) 且 \(a_{p_1} < a_{p_2} < a_{p_3} < \cdots < a_{p_m}\) ,并使得 \(\sum_{i = 1}^{m} a_{p_i}\) 最大。
\(1 \leq n \leq 2 \times 10^{5}, 1 \leq a_i \leq 2 \times 10^{5}\) 。
题解:
最优结果,考虑暴力 DP 怎么做。设以 \(i\) 为结尾,最大和上升子序列结果为 \(f_{i}\) ,则有 \(f_{i} = f_{j} + a_{i}\ s.t.\ j < i, a_j < a_i, max(f_{j})\) 。
显然的二维前缀偏序:
于是用 Fenwick Tree 维护 DP 。
如果 \(a\) 的值域很大也能做,\(a_x\) 从离散数组 \(numy\) 中离散化出的结果为 \(py\) ,即有 \(a_i = numy[py - 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] = 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
题解:
- 将操作和询问离线并排序。
- 扫描线直接扫描二维问题时,通常顺序扫描 \(y\) 轴方便。于是点的竖直优先级最高。
- 次优询问和修改的顺序。点的“水平优先级、所属询问的 \(id\)” 顺序不重要。
修改直接插入一个点,不妨编号成第 \(0\) 个询问。
询问矩形 \((x_1, y_1), (x_2, y_2)\) ,则插入 \((x_2, y_2), (x_1 - 1, x_2 - 1), (x_2, y_1 - 1), (x_1 - 1, y_2)\) 用来容斥矩形,编号第 \(i\) 个询问。
询问优先级:
- 四个点排序优先级是前两个减去后两个。
- 询问优先级低于修改。
扫描线只需要离散化另一维:由于事件被排序成了从 \(y\) 轴向上扫描。只需要离散化 \(x\) 轴的需要修改的坐标。
遇到修改事件时,进行修改。优先级高于询问事件。
遇到询问事件时,调整事件对应询问的答案。
询问时的二维偏序是:
修改时往 Fenwick Tree 插入一个点。
一个开闭区间离散化的技术处理:
正常情况下在离散数组中直接 lower_bound[x] 就能找到 x 离散出的值。
但是容斥矩形的四个点存在开闭区间的处理,只看 \(x\) ,有 \(x\) 或者 \(x - 1\) 。但 \(x - 1\) 并不在离散数组中。
对 \(\leq (x - 1)_{open}\) 的询问,实则等同于开区间 \(x - 1\) 左侧一个点 \(\leq (x^{'})_{closed}\) 的询问。
这时候 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
题意:
给 \(n\) 个数 \(a_1, a_2, a_3, \cdots, a_n\) 。
\(q\) 个询问,每次询问区间 \([l, r]\) 里不同数字之和(即一个数字的权值只算一次加法贡献)。
\(1 \leq n,q \leq 2 \times 10^{5}, 1 \leq a_i \leq n, 1 \leq l \leq r \leq n\) 。
题解:
\(a_i \leq n\) 没啥意义,和偏序无关。
需要一个技术处理:
技术处理: \(for\ i = 1\ to\ n\) ,在线维护 \(pre_i\) 为上一个 \(a_i\) 出现的位置。
具体需要处理一个 last[a[i]] = i, pre[i] = last[a[i]] 。
存在两种方法:
方法 1:
狭义扫描线
需要离线的,非全序的,二维加法偏序。都等价于离线的二维数点,都能用狭义扫描线处理。
全序显然比非全序更好解决,直接广义扫描线就能处理。
观察任意区间 \([l, r]\),若 \(i \in [l, r]\) ,则 \(pre_i < l\) 代表 \(a_i\) 在 \([l, r]\) 中是第一次出现。
这是个经典的处理,类似的有:https://www.luogu.com.cn/problem/P8773(选数异或)
即是一个二维偏序:
这个区间只要用两个矩形就能容斥。
\(pre_i\) 为 \(y\) 轴,\(i\) 为 \(x\) 轴,扫描线沿 \(y\) 轴向上扫。修改和询问 \(x\) 轴上的点。
event 记录 x,y,操作优先级,操作 id 。确保 y 为第一优先级(扫描轴),操作优先级为第二优先级。其他优先级不重要。
- 修改:for i = 1 to n,\(\{pre_i, 0, i, 0\}\) 。
- 询问:for i = 1 to q,\(\{l - 1, 1, r, i\},\ \{l - 1, 2, l, i\}\) 。
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 作为更新点。
观察任意区间 \([l, r]\) 。对每个 \(l\) 维护 \(ans_l\) 表示,\(l\) 到 \(r\) 的答案。
考虑更新:\(r - 1 \rightarrow r\) 时,所有 \(ans_x\) 受到的影响。
观察到任意某个区间 \([l, r]\) ,若 \(a_r\) 没出现过,则 \(ans\) 受到的影响为: \(x \in [l, r],\ ans_x += a_r\) 。
显然 \(a_r\) 没出现过的区间是 \([pre_r + 1, r]\) ,有 \(ans_x += a_r\ s.t.\ pre_r + 1 \leq x \leq r\) 。
利用树状数组区间修改,单点查询。
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;
}
六 高维树状数组
每一维单次操作的时间复杂度 \(O(\log n)\) ,所以高维树状数组的单次操作的复杂度 \(O((\log n)^{x})\) 。
6.1 二维单点修改,区间查询 http://oj.daimayuan.top/course/15/problem/637
题意:
给 \(n \times m\) 个数 \(a_{1, 1}, a_{1, 2}, a_{1, 3}, \cdots, a_{1, m}, \cdots, a_{n, m}\) 。
支持 \(q\) 个操作:
- 1 x y d ,修改 \(a_x, y = d\) 。
- 2 x y ,查询 \(\sum_{i = 1}^{x} \sum_{j = 1}^{y} a_{i, j}\) 。
\(1 \leq n, m \leq 500, 1 \leq q \leq 2 \times 10^{5}, a_{i, j} 1 \leq a_{i, j} \leq 10^{9}\) 。
\(1 \leq x \leq n, 1 \leq y \leq m, 1 \leq d \leq 10^{9}\) 。
题解:
就是很朴素的二维区间上维护树状数组,每一维都基于 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
题意:
给一个大小为 \(n \times m\) 的 \(0\) 矩阵。直到文件输入结束,执行若干个操作。
操作有两类:
- 1 a b c d x ,表示将右上角 \((a, b)\) 到左下角 \((c, d)\) 的子矩阵加上 \(x\) 。
- 2 a b c d ,表示询问右上角 \((a, b)\) 到左下角 \((c, d)\) 的子矩阵数字和。
令其是笛卡尔坐标系。给的是零矩阵,不需要关注端点。
否则笛卡尔坐标系的前缀是左下角,不妨 \(swap(a, c)\) 。
题解:
微笑着推式子吧:
建立四个树状数组 \(P, Q, H, W\) 。
询问时分别补上 \(\times (x + 1)(y + 1), \times x, \times y, \times 1\) 的贡献。这是显然的。
修改时分别补上 \(\times 1, \times y, \times x, \times xy\) 的贡献。考虑原数组对树状数组的贡献。
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;
}