0x42 树状数组
何为树状数组?
首先,树状数组是用来维护序列的前缀和的。
其次,我们要知道树状数组是如何将大区间拆分成一堆小区间的。
比如 \(7 = 111_{(2)}\),我们可以将其拆分为 \([1,4], [5, 6], [7, 7]\),再比如 \(12 = 1100_{2}\),我们可以将其拆分为 \([1, 8],[9, 12]\)。
所以对于一个数 \(x\),我们每次可以将其拆分为 \([x - lowbit(x) + 1, x]\) 的小区间,然后将 \(x = x - lowbit(x)\)。
那么对于树状数组 \(tr[x]\) 来说,它存的就是 \([x - lowbit(x) + 1, x]\) 的和,比如 \(tr[6]\) 存的是 \([5, 6]\)。
如何查询呢?
假设我们要查询 \([1, x]\) 的前缀和,我们可以从 \(x\) 开始:
- 加上 \(tr[x]\)
- 令 \(x = x - lowbit(x)\)
- 循环以上操作,直到 \(x = 0\)。
一个简单的例子:\([1,15]\) 的前缀和就等于 \(tr[15] + tr[14] + tr[12] + tr[8]\)
如何单点增加呢?
假设我们要在 \(A_x\) 上加 \(y\) 我们只要让所有包含 \(A_x\) 的 \(tr\) 加上 \(y\) 即可。集体操作如下:
- \(tr[x]\) 加上 \(y\)
- 令 \(x = x + lowbit(x)\)
- 循环以上操作,直到 \(x > n\)。
如何建树呢?
假设要对于 \(A_1\sim A_n\) 构造一个树状数组,那么我们只要对于每个 \(i\) 都做一次单点修改,加上 \(A_i\) 即可。
如何区间修改呢?
我们可以使用差分,用树状数组维护差分数组 \(b\),每次 \(b_l + d, b_{r + 1} - d\),就相当于 \(add(l, d)\) 和 \(add(r + 1, -d)\)。
如何区间查询呢?
我们观察 y 总画的图,其中 \(b\) 是差分数组。我们发现,蓝色就等于一整个方框减去红色,即:
\((x+1)\sum^{n}_{i = 1}b_i - \sum^{n}_{i = 1}i \times b_i\)
我们可以用两个树状数组分别维护 \(\sum^{n}_{i = 1}b_i\) 和 \(\sum^{n}_{i = 1}i \times b_i\)。
树状数组可以求逆序对
假设有一个序列 \(A\),我们在 \(A\) 的值域范围上构造树状数组,即对于每个 \(A_i\) 执行一遍 \(add(A_i, 1)\)。
统计逆序对的时候,我们只要返回 \(query(A_i - 1)\) 即可。
这个复杂度是 \(O(N+M)logM\) 的,其中 \(M\) 是值域大小。
来看一些蓝书上的例题。
241. 楼兰图腾 - AcWing题库
我们运用类似于求逆序对的思路,对于 \(A_i\),求出 \(A_1 \sim A_{i - 1}\) 内小于它的数的数量 \(small1[i]\) 和大于的数量 \(big1[i]\),再倒着求出 \(A_{i + 1} \sim A_n\) 内的 \(small2[i]\) 和 \(big2[i]\)。
我们以 ^
为例,它的数量就是 \(\sum^{n}_{i = 1} small1[i] \times small2[i]\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int n, a[N];
LL tr[N], big[N], small[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, int c) {
for (; x <= n; x += lowbit(x)) tr[x] += c;
}
LL query(int x) {
LL res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++) {
int x = a[i];
big[i] = query(n) - query(x);
small[i] = query(x - 1);
add(x, 1);
}
memset(tr, 0, sizeof tr);
LL res1 = 0, res2 = 0;
for (int i = n; i; i --) {
int x = a[i];
res1 += big[i] * (query(n) - query(x));
res2 += small[i] * query(x - 1);
add(x, 1);
}
printf("%lld %lld\n", res1, res2);
return 0;
}
242. 一个简单的整数问题 - AcWing题库
就是上文中提到的区间修改+单点查询。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int a[N], tr[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, int c) {
for (; x <= n; x += lowbit(x)) tr[x] += c;
}
int query(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++) {
int b = a[i] - a[i - 1];
add(i, b);
}
while (m --) {
char op[2];
scanf("%s", op);
if (*op == 'C') {
int l, r, d; scanf("%d%d%d", &l, &r, &d);
add(l, d); add(r + 1, -d);
} else {
int x; scanf("%d", &x);
printf("%d\n", query(x));
}
}
return 0;
}
243. 一个简单的整数问题2 - AcWing题库
区间查询+区间修改
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m, a[N];
LL tr1[N], tr2[N];
int lowbit(int x) {
return x & -x;
}
void add(LL tr[], int x, LL c) {
for (; x <= n; x += lowbit(x)) tr[x] += c;
}
LL query(LL tr[], int x) {
LL res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
LL prefix_sum(int x) {
return query(tr1, x) * (x + 1) - query(tr2, x);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++) {
int b = a[i] - a[i - 1];
add(tr1, i, b);
add(tr2, i, (LL)i * b);
}
while (m --) {
char op[2]; int l, r, d;
scanf("%s%d%d", op, &l, &r);
if (*op == 'C') {
scanf("%d", &d);
add(tr1, l, d); add(tr2, l, (LL)l * d);
add(tr1, r + 1, -d); add(tr2, r + 1, (LL)(r + 1) * -d);
} else {
printf("%lld\n", prefix_sum(r) - prefix_sum(l - 1));
}
}
return 0;
}
244. 谜一样的牛 - AcWing题库
很有意思的一道题。
我们发现对于 \(i\) 来说,它对应的牛就应该是剩下未选择的牛中第 \(A_i + 1\) 大的牛。
什么是未选择的牛?假设当前牛为 \(1 \sim 5\),且已经选择了 \(2, 4\),那么 \(1,3,5\) 就是未选择的牛,\(3\) 就是第二大的牛。
想通了之后就很好维护了,我们维护一个树状数组,最开始从 \(1\sim n\) 执行 \(add(i, 1)\),然后每选择一只牛 \(x\) 就 \(add(x, -1)\)。我们发现 \(query(x)\) 就是当前 \(1\sim x\) 中未选择牛的数量。找第 \(A_i + 1\) 大的牛就显然可以二分。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, a[N];
int tr[N], ans[N];
int lowbit(int x) {
return x & -x;
}
void add(int x, int c) {
for (; x <= n; x += lowbit(x)) tr[x] += c;
}
int query(int x) {
int res = 0;
for (; x; x -= lowbit(x)) res += tr[x];
return res;
}
int main() {
scanf("%d", &n);
for (int i = 2; i <= n; i ++) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++) add(i, 1);
for (int i = n; i; i --) {
int l = 1, r = n;
while (l < r) {
int mid = l + r >> 1;
if (query(mid) < a[i] + 1) l = mid + 1;
else r = mid;
}
add(l, -1);
ans[i] = l;
}
for (int i = 1; i <= n; i ++) printf("%d\n", ans[i]);
return 0;
}
0x43 线段树
何为线段树?
线段树和树状数组不同,它维护的是一个个子序列。
如上图,对于一个区间 \([l, r]\),它的左儿子就是 \([l, mid]\),右儿子就是 \([mid + 1, r]\),其中 \(mid = \frac{l+r}{2}\)。
我们可以给线段树上的每一个结点编号,假设父节点编号为 \(x\),左儿子编号就是 \(x\times2\),右儿子编号就是 \(x\times2+1\)。
struct Stree {
int l, r, v;
} tr[N * 4];
以上代码是最简单的线段树,其中 \(l, r\) 为区间的左右端点,\(v\) 为区间内所有数的最大值。
然后我们来说如何维护线段树。
首先,建树。
我们可以参照上图,我们从根节点开始,每次分别递归左儿子和右儿子,直到叶节点。此时它的最大值就应该是它本身。然后我们回溯到父节点,由于我们已经对它的儿子都做过了,所以我们可以用儿子的值求出父亲,即父亲的最大值可以通过儿子的最大值推出来。
void pushup(int u) {
tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) return;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
build(1, 1, n);
以上代码中,\(build\) 就是建树,\(pushup\) 就是用儿子来更新父亲。
有的时候我们可能会这样写 \(build\)。
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, w[r]};
else {
int mid = l + r >> 1;
tr[u] = {l, r};
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
此时请注意 \(else\) 中也要为 \(tr[u]\) 所对应区间赋值。
\(build\) 是 \(O(nlogn)\) 的
接着,我们考虑单点修改。
假设当前我们要修改下标为 \(x\) 的数,那么从根节点开始,对于一个父节点, \(x\) 一定在它的左儿子或是右儿子。我们只要从中选择一个即可。
修改完儿子后,我们同样要 \(pushup\)。
void modify(int u, int x, int val) {
if (tr[u].l == x && tr[u].r == x) {
tr[u].v = val;
} else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, val);
if (x > mid) modify(u << 1 | 1, x, val);
pushup(u);
}
}
\(modify\) 显然是 \(O(logn)\) 的。
然后是区间查询。
区间查询就是对于一个区间 \([l, r]\) 询问它的最大值,总和,或是其它线段树维护的值,这里以最大值为例。
对于线段树上的一个结点 \(u\),我们可以分四种情况:
- \(u\) 存储的区间被 \([l, r]\) 包含,那么我们直接范围 \(u\) 即可。
- \(u\) 属于 \([l, mid]\),那么我们递归左儿子。
- \(u\) 属于 \([mid + 1, r]\),我们递归右儿子。
- \(u\) 横跨左右儿子,我们左右一起递归。
下面是一个简便写法。
int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].v;
int mid = tr[u].l + tr[u].r >> 1;
int s = 0;
if (l <= mid) s = query(u << 1, l, r);
if (r > mid) s = max(s, query(u << 1 | 1, l, r));
return s;
}
为什么要分四种情况讨论呢?因为有的题目需要我们维护一些横跨两个子区间的信息,所以需要在第四种情况时进行额外的操作。
由于答案一般能够分成 \(O(logn)\) 个区间,所以 \(query\) 的时间复杂度是 \(O(logn)\) 的。
然后是比较困难的区间修改。
我们首先可以想到对于区间的每个点进行单点修改,但这样过于慢。我们发现,如果对一段区间进行修改后,它的儿子的值也都会变化,所以在最坏情况下我们会遍历整棵树。
这里我们引入懒标记,即为了偷懒而生的标记。我们在维护线段树上结点的信息的同时,再维护一个 \(add\)(这里以区间增加为例),意思是我们需要给该结点的所有儿子都加上 \(add\)(儿子是一个序列,如果长为 \(k\),那就相当于序列中的每个数都加 \(add\),即总和加 \(k\times add\))。
我们在执行区间修改的时候,如果某个结点表示的区间被询问的区间包含,那么就修改它的懒标记,同时更改这个区间的和。
void pushdown(int u) {
STree &left = tr[u << 1], &right = tr[u << 1 | 1], &root = tr[u];
if (root.add) {
// 给区间内的每个数加上 add,那么区间的总和就要加上区间长度 * add
left.add += root.add, left.s += (LL)(left.r - left.l + 1) * root.add;
right.add += root.add, right.s += (LL)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void modify(int u, int l, int r, int d) {
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].add += d;
tr[u].s += (LL)(tr[u].r - tr[u].l + 1) * d;
} else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);
}
}
LL query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].s;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL res = 0;
if (l <= mid) res += query(u << 1, l, r);
if (r > mid) res += query(u << 1 | 1, l, r);
return res;
}
我们发现在 \(modify\) 中多出了一个 \(pushdown\),它的含义就是将父亲的懒标记下传,因为此时我们要遍历子区间了。
同时查询操作也要加上 \(pushdown\) 操作。
245. 你能回答这些问题吗 - AcWing题库
我们考虑在线段树中维护 \(lmax\),意思是当前区间最大连续前缀和,同样维护 \(rmax\)。
此时父节点的最大连续子段和就是子节点的最大连续子段和,以及左儿子的 \(rmax\) 加上右儿子的 \(lmax\) 的最大值。
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int n, m;
int a[N];
struct Stree {
int l, r, s, smax, lmax, rmax;
} tr[N * 4];
void pushup(Stree &u, Stree &a, Stree &b) {
u.s = a.s + b.s;
u.lmax = max(a.lmax, a.s + b.lmax);
u.rmax = max(b.rmax, a.rmax + b.s);
u.smax = max({a.smax, b.smax, a.rmax + b.lmax});
}
void pushup(int u) {
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, a[r], a[r], a[r], a[r]};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int x, int v) {
if (tr[u].l == x && tr[u].r == x) tr[u].s = tr[u].smax = tr[u].lmax = tr[u].rmax = v;
else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
}
Stree query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
else {
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
else if (l > mid) return query(u << 1 | 1, l, r);
else {
auto a = query(u << 1, l, r), b = query(u << 1 | 1, l, r);
Stree c;
pushup(c, a, b);
return c;
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
build(1, 1, n);
while (m --) {
int op, l, r;
scanf("%d%d%d", &op, &l, &r);
if (op == 1) {
if (l > r) swap(l, r);
printf("%d\n", query(1, l, r).smax);
} else {
modify(1, l, r);
}
}
return 0;
}
246. 区间最大公约数 - AcWing题库
由更相减损术我们可以知道:
\[\gcd(w[i], w[i + 1], w[i + 2], ..., w[j]) = gcd(w[i], w[i + 1] - w[i], w[i + 2] - w[i - 1], ..., w[j] - w[j - 1]) \]这其实是差分的形式,这启发我们用线段树维护差分。
具体我们可以在线段树中维护差分,具体地我们维护一个 \(s\) 表示 \([l, r]\) 的差分和,再维护一个 \(d\) 表示 \([l, r]\) 的最大公约数。
那么原来的式子就可以表示为 \(gcd(w[i], gcd(b[i + 1], ..., b[j])) = gcd(query(1, i).s, query(i + 1, j).d)\),其中 \(query\) 返回的是一整个线段树结点。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 500010;
int n, m;
LL w[N];
struct Stree {
int l, r;
LL s, d;
} tr[N * 4];
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : abs(a);
}
void pushup(Stree &u, Stree &l, Stree &r) {
u.s = l.s + r.s;
u.d = gcd(l.d, r.d);
}
void pushup(int u) {
pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, w[r] - w[l - 1], w[r] - w[l - 1]};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int x, LL v) {
if (tr[u].l == x && tr[u].r == x) tr[u] = {x, x, tr[u].s + v, tr[u].s + v};
else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, v);
else modify(u << 1 | 1, x, v);
pushup(u);
}
}
Stree query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
else {
int mid = tr[u].l + tr[u].r >> 1;
if (l > mid) return query(u << 1 | 1, l, r);
else if (r <= mid) return query(u << 1, l, r);
else {
auto a = query(u << 1, l, r), b = query(u << 1 | 1, l, r);
Stree c;
pushup(c, a, b);
return c;
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) scanf("%lld", &w[i]);
build(1, 1, n);
while (m --) {
char op[2]; int l, r; LL d;
scanf("%s%d%d", op, &l, &r);
if (*op == 'Q') {
if (l == r) printf("%lld\n", query(1, 1, l).s);
else {
auto a = query(1, 1, l), b = query(1, l + 1, r);
printf("%lld\n", gcd(a.s, b.d));
}
} else {
scanf("%lld", &d);
modify(1, l, d);
if (r + 1 <= n) modify(1, r + 1, -d);
}
}
return 0;
}
243. 一个简单的整数问题2 - AcWing题库
懒标记模板。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m, w[N];
struct STree {
int l, r;
LL s, add;
} tr[N * 4];
void pushup(int u) {
tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
}
void pushdown(int u) {
STree &left = tr[u << 1], &right = tr[u << 1 | 1], &root = tr[u];
if (root.add) {
left.add += root.add, left.s += (LL)(left.r - left.l + 1) * root.add;
right.add += root.add, right.s += (LL)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, w[r], 0};
else {
int mid = l + r >> 1;
tr[u] = {l, r};
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int d) {
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].add += d;
tr[u].s += (LL)(tr[u].r - tr[u].l + 1) * d;
} else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);
}
}
LL query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].s;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
LL res = 0;
if (l <= mid) res += query(u << 1, l, r);
if (r > mid) res += query(u << 1 | 1, l, r);
return res;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++) scanf("%d", &w[i]);
build(1, 1, n);
while (m --) {
char op[2]; int l, r;
scanf("%s%d%d", op, &l, &r);
if (*op == 'C') {
int d; scanf("%d", &d);
modify(1, l, r, d);
} else {
printf("%lld\n", query(1, l, r));
}
}
return 0;
}
何为扫描线?
假设当前平面内有一堆的矩形,我们要统计它们的面积(不能重复),那么我们就可以像下图一样,用一条平行 \(y\) 轴的扫描线从左往右,扫。根据下图,总面积就应该是 \(\sum^{2n - 1}_{i = 1}h_i \times (x_{i + 1} - x_i)\),其中 \(n\) 为矩形的数量,\(h_i\) 为扫描线上覆盖有矩形的长度。
具体地,我们把每扫到一个矩形的左宽,就把左宽覆盖的区间 \(+1\),扫到右宽就 \(-1\),这样就可以算出 \(h_i\)。
我们发现该操作设计区间修改和区间查询,所以我们考虑用线段树加速。
线段树中存储 \(cnt\) 表示直接完全覆盖此区间的矩形数量,\(len\) 表示此区间被矩形覆盖的长度。
并且注意,线段树上的每个叶节点存的都是区间,所以需要注意坐标。
根据以上的存储方式,可能会出现子节点的 \(cnt\) 大于父节点的 \(cnt\) 的情况。
我们再考虑 \(pushup\) 操作如何写。分三种情况:
- \(cnt \ne 0\),说明该区间被完全覆盖,\(len\) 就为该区间的长度。
- \(cnt = 0\),且 \(l \ne r\),可以用左儿子和右儿子来更新自己。
- \(cnt = 0\),且 \(l = r\),不合法,无用线段,\(len = 0\)
我们可以通过一些手段证明 \(pushdown\) 某些时候是不用写的。(另外一些时候比如第二道例题要写,下文会提到)。
引用的证明:
此时 \(cnt\) 表达的含义:当前区间被覆盖的次数,跟其它节点无关。
可以发现,因为对于修改区间 \([l, r]\) 操作,是一对一对的。
所以,一个节点代表的区间被覆盖的次数不需要继承其父亲信息的情况。
因此需要去掉 \(pushDown\)。
我们 \(Atlantis\) 一题为例。
247. 亚特兰蒂斯 - AcWing题库
在上文的思路上,由于本题坐标可能有小数,所以需要进行离散化。
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n;
vector<double> tmp;
struct Segment {
double x, y1, y2;
int k;
bool operator < (const Segment &t) const{
return x < t.x;
}
} segs[N * 2];
struct Stree {
int l, r, cnt;
double len;
} tr[N * 8];
// tmp 存的是小数,它在 tmp 中的位置就是离散化的值
// find 查找的是某个小数被映射到了什么位置
int find(double v) {
return lower_bound(tmp.begin(), tmp.end(), v) - tmp.begin();
}
void pushup(int u) {
if (tr[u].cnt) {
tr[u].len = tmp[tr[u].r + 1] - tmp[tr[u].l];
} else if (tr[u].l != tr[u].r) {
tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
} else {
tr[u].len = 0;
}
}
void build(int u, int l, int r) {
if (l == r) tr[u] = {l, r, 0, 0};
else {
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
}
}
void modify(int u, int l, int r, int d) {
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].cnt += d;
pushup(u);
} else {
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);
}
}
int main() {
int T = 0;
while (scanf("%d", &n), n) {
tmp.clear();
for (int i = 1, j = 0; i <= n; i ++) {
double x_1, y_1, x_2, y_2;
scanf("%lf%lf%lf%lf", &x_1, &y_1, &x_2, &y_2);
segs[j ++] = {x_1, y_1, y_2, 1};
segs[j ++] = {x_2, y_1, y_2, -1};
tmp.push_back(y_1); tmp.push_back(y_2);
}
sort(segs, segs + 2 * n);
sort(tmp.begin(), tmp.end());
tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
build(1, 0, tmp.size() - 2);
double res = 0;
for (int i = 0; i < n * 2; i ++) {
if (i > 0) res += tr[1].len * (segs[i].x - segs[i - 1].x);
modify(1, find(segs[i].y1), find(segs[i].y2) - 1, segs[i].k);
}
printf("Test case #%d\n", ++ T);
printf("Total explored area: %.2lf\n\n", res);
}
return 0;
}
248. 窗内的星星 - AcWing题库
题解 P1502 【窗口的星星】 - 洛谷专栏 (luogu.com.cn)
思路这篇题解说的很明白,我们来谈谈这个问题为什么需要懒标记。
最重要的一点是,我们线段树中存的数据不再是 \(cnt\) 和 \(len\),而是 \(sm\) 表示的该子区间对应的亮度总和。亮度的增加不同于 \(cnt\) 是要下传的。并且线段树中叶子存的区间不再是 \([l, l+1]\) 而是 \([l, l]\),可以理解为一个点。所以我们需要 \(lasytag\) 来帮助操作。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 10010;
int n, w, h;
vector<LL> tmp;
struct Segment {
LL x, l, r, c;
bool operator < (const Segment &t) const{
return x < t.x || x == t.x && c > t.c;
}
} segs[N * 2];
struct Stree {
int l, r;
LL sm, add;
} tr[N * 8];
LL find(LL x) {
return lower_bound(tmp.begin(), tmp.end(), x) - tmp.begin();
}
void pushup(int u) {
tr[u].sm = max(tr[u << 1].sm, tr[u << 1 | 1].sm);
}
void pushdown(int u) {
if (tr[u].add) {
tr[u << 1].sm += tr[u].add;
tr[u << 1].add += tr[u].add;
tr[u << 1 | 1].sm += tr[u].add;
tr[u << 1 | 1].add += tr[u].add;
tr[u].add = 0;
}
}
void build(int u, int l, int r) {
tr[u] = {l, r, 0, 0};
if (l != r) {
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
}
void modify(int u, int l, int r, int d) {
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].sm += d, tr[u].add += d;
} else {
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while (cin >> n >> w >> h) {
tmp.clear();
for (int i = 1, j = 0; i <= n; i ++) {
LL x, y, c;
cin >> x >> y >> c;
segs[j ++] = {x, y, y + h - 1, c};
segs[j ++] = {x + w - 1, y, y + h - 1, -c};
tmp.push_back(y); tmp.push_back(y + h - 1);
}
sort(tmp.begin(), tmp.end());
tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
sort(segs, segs + n * 2);
build(1, 0, tmp.size() - 1);
LL res = 0;
for (int i = 0; i < n * 2; i ++) {
modify(1, find(segs[i].l), find(segs[i].r), segs[i].c);
res = max(res, tr[1].sm);
}
cout << res << endl;
}
return 0;
}
标签:tmp,int,LL,tr,mid,0x40,蓝书,区间
From: https://www.cnblogs.com/biyimouse/p/18607794