From 982c371c58acdb42bced8ac0d4fd406603ba37ba Mon Sep 17 00:00:00 2001 From: Zirnc Date: Thu, 18 Aug 2022 10:26:23 +0800 Subject: [PATCH 1/4] Create segment-tree-beats.md --- content/articles/segment-tree-beats.md | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 content/articles/segment-tree-beats.md diff --git a/content/articles/segment-tree-beats.md b/content/articles/segment-tree-beats.md new file mode 100644 index 0000000..8a82e35 --- /dev/null +++ b/content/articles/segment-tree-beats.md @@ -0,0 +1,37 @@ +--- +title: Segment Tree Beats 吉司机线段树笔记 +tags: +- OI +- 算法 +- 数据结构 +- 线段树 +- Segment Tree Beats +categories: 学习笔记 +date: 2022-08-18 +katex: true +draft: true +--- + +## 区间最值操作(chmin/chmax) + +Segment Tree Beats 的一个重要应用就是处理区间最值操作,例如 chmin:$\forall i \in [l, r], a_i = min(a_i, x)$。chmax 同理。 + +引出例题:[HDU5306 Gorgeous Sequence](https://acm.hdu.edu.cn/showproblem.php?pid=5306) + +> 维护一个序列 $a$: +> +> 1. `0 l r t` $\forall l\le i\le r,\ a_i=\min(a_i,t)$。 +> 2. `1 l r` 输出区间 $[l,r]$ 中的最大值。 +> 3. `2 l r` 输出区间和。 +> +> 多组数据,$T\le 100,n\le 10^6,\sum m\le 10^6$。 + +对于这个问题,用普通的线段树难以解决。因为 chmin 操作修改的数只有大于 $t$ 的数,而其它则不变,并不是对整个区间做修改。 + +吉老师线段树的每个结点需要额外维护:最大值 $mx$,次大值 $se$ 和最大值个数 $cnt$。在区间 chmin 时要做以下操作: + +1. $t > mx$,显然这一操作是没有意义的,直接返回; +2. $se < t <= mx$,那么这一区间内的最大值需要变小成为 $t$。所以区间和 $sum$ 减去 $(mx-t)\times cnt$,更新 $mx$ 为 $t$,并做标记。 +3. $t <= se$,不止有一个值会受到影响,需要递归处理左右子树,最后记得上传信息。 + +使用势能分析法可以得到复杂度是 $O(m\log n)$。 From 178833a02b013addac23da121c6c7eaaaef45c2e Mon Sep 17 00:00:00 2001 From: Zirnc Date: Thu, 18 Aug 2022 11:43:07 +0800 Subject: [PATCH 2/4] Update segment-tree-beats.md --- content/articles/segment-tree-beats.md | 113 +++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/content/articles/segment-tree-beats.md b/content/articles/segment-tree-beats.md index 8a82e35..b635e8f 100644 --- a/content/articles/segment-tree-beats.md +++ b/content/articles/segment-tree-beats.md @@ -35,3 +35,116 @@ Segment Tree Beats 的一个重要应用就是处理区间最值操作,例如 3. $t <= se$,不止有一个值会受到影响,需要递归处理左右子树,最后记得上传信息。 使用势能分析法可以得到复杂度是 $O(m\log n)$。 + +------ + +下面讲讲代码实现。 + + + + +[RECORD](https://vjudge.net/solution/37834417) + +```cpp +#include +using namespace std; +using LL = long long; +const int MAXN = 1000000, MAXM = 1000000; +int maxx[MAXN << 2], se[MAXN << 2], cnt[MAXN << 2], tag[MAXN << 2]; +LL sum[MAXN << 2]; +int n, m; +int a[MAXN]; +void pushup(int u) { + sum[u] = sum[u << 1] + sum[u << 1 | 1]; + if (maxx[u << 1] > maxx[u << 1 | 1]) { + maxx[u] = maxx[u << 1]; + se[u] = max(maxx[u << 1 | 1], se[u << 1]); + cnt[u] = cnt[u << 1]; + } else if (maxx[u << 1] < maxx[u << 1 | 1]) { + maxx[u] = maxx[u << 1 | 1]; + se[u] = max(maxx[u << 1], se[u << 1 | 1]); + cnt[u] = cnt[u << 1 | 1]; + } else { // == + maxx[u] = maxx[u << 1]; + se[u] = max(se[u << 1], se[u << 1 | 1]); + cnt[u] = cnt[u << 1] + cnt[u << 1 | 1]; + } +} +void pushtag(int u, int x) { + if (maxx[u] <= x) return; + sum[u] -= (1ll * maxx[u] - x) * cnt[u]; + maxx[u] = tag[u] = x; +} +void pushdown(int u) { + if (tag[u] == -1) return; + pushtag(u << 1, tag[u]); + pushtag(u << 1 | 1, tag[u]); + tag[u] = -1; +} +void build(int u, int l, int r) { + tag[u] = -1; + if (l == r) { + maxx[u] = sum[u] = a[l]; + cnt[u] = 1; + se[u] = -1; + return; + } + int mid = (l + r) >> 1; + build(u << 1, l, mid); + build(u << 1 | 1, mid + 1, r); + pushup(u); +} +void modify(int u, int l, int r, int x, int y, int t) { + if (maxx[u] <= t) return; + if (l >= x && r <= y && se[u] < t) { + pushtag(u, t); + return; + } + pushdown(u); + int mid = (l + r) >> 1; + if (x <= mid) modify(u << 1, l, mid, x, y, t); + if (y > mid) modify(u << 1 | 1, mid + 1, r, x, y, t); + pushup(u); +} +int querymax(int u, int l, int r, int x, int y) { + if (l >= x && r <= y) return maxx[u]; + int ans = -1e9; + pushdown(u); + int mid = (l + r) >> 1; + if (x <= mid) ans = max(ans, querymax(u << 1, l, mid, x, y)); + if (y > mid) ans = max(ans, querymax(u << 1 | 1, mid + 1, r, x, y)); + return ans; +} +LL querysum(int u, int l, int r, int x, int y) { + if (l >= x && r <= y) return sum[u]; + LL ans = 0; + pushdown(u); + int mid = (l + r) >> 1; + if (x <= mid) ans += querysum(u << 1, l, mid, x, y); + if (y > mid) ans += querysum(u << 1 | 1, mid + 1, r, x, y); + return ans; +} +int main() { + //ios::sync_with_stdio(false); + int t; + scanf("%d", &t); + while (t--) { + 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, t; + scanf("%d%d%d", &op, &l, &r); + if (op == 0) { + scanf("%d", &t); + modify(1, 1, n, l, r, t); + } else if (op == 1) { + printf("%d\n", querymax(1, 1, n, l, r)); + } else { + printf("%lld\n", querysum(1, 1, n, l, r)); + } + } + } + return 0; +} +``` From 9ff4e5f59188ff00fd744f7085bc5682ea82e647 Mon Sep 17 00:00:00 2001 From: Zirnc Date: Thu, 18 Aug 2022 13:32:44 +0800 Subject: [PATCH 3/4] Update segment-tree-beats.md --- content/articles/segment-tree-beats.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/content/articles/segment-tree-beats.md b/content/articles/segment-tree-beats.md index b635e8f..3164e3f 100644 --- a/content/articles/segment-tree-beats.md +++ b/content/articles/segment-tree-beats.md @@ -36,12 +36,7 @@ Segment Tree Beats 的一个重要应用就是处理区间最值操作,例如 使用势能分析法可以得到复杂度是 $O(m\log n)$。 ------- - -下面讲讲代码实现。 - - - +实现时,需要留心 `pushdown` 和 `pushup` 函数,步骤较多。用 `pushtag` 函数给当前结点加上 tag。 [RECORD](https://vjudge.net/solution/37834417) @@ -54,7 +49,7 @@ int maxx[MAXN << 2], se[MAXN << 2], cnt[MAXN << 2], tag[MAXN << 2]; LL sum[MAXN << 2]; int n, m; int a[MAXN]; -void pushup(int u) { +void pushup(int u) { // 维护 u 的信息 sum[u] = sum[u << 1] + sum[u << 1 | 1]; if (maxx[u << 1] > maxx[u << 1 | 1]) { maxx[u] = maxx[u << 1]; @@ -70,12 +65,12 @@ void pushup(int u) { cnt[u] = cnt[u << 1] + cnt[u << 1 | 1]; } } -void pushtag(int u, int x) { +void pushtag(int u, int x) { // 给当前点加 tag if (maxx[u] <= x) return; - sum[u] -= (1ll * maxx[u] - x) * cnt[u]; - maxx[u] = tag[u] = x; + sum[u] -= (1ll * maxx[u] - x) * cnt[u]; // 更新 sum + maxx[u] = tag[u] = x; // 更新 maxx、tag } -void pushdown(int u) { +void pushdown(int u) { // 下放 tag if (tag[u] == -1) return; pushtag(u << 1, tag[u]); pushtag(u << 1 | 1, tag[u]); @@ -86,7 +81,7 @@ void build(int u, int l, int r) { if (l == r) { maxx[u] = sum[u] = a[l]; cnt[u] = 1; - se[u] = -1; + se[u] = -1; // 次大值初始化为 -1 return; } int mid = (l + r) >> 1; @@ -96,7 +91,7 @@ void build(int u, int l, int r) { } void modify(int u, int l, int r, int x, int y, int t) { if (maxx[u] <= t) return; - if (l >= x && r <= y && se[u] < t) { + if (l >= x && r <= y && se[u] < t) { // 该区间的最大值都变为 t pushtag(u, t); return; } From eb737fe0d36de3a00141d2bbb8ce832b3db18907 Mon Sep 17 00:00:00 2001 From: Zirnc Date: Thu, 18 Aug 2022 14:50:04 +0800 Subject: [PATCH 4/4] Update segment-tree-beats.md --- content/articles/segment-tree-beats.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/content/articles/segment-tree-beats.md b/content/articles/segment-tree-beats.md index 3164e3f..9e16c73 100644 --- a/content/articles/segment-tree-beats.md +++ b/content/articles/segment-tree-beats.md @@ -120,7 +120,6 @@ LL querysum(int u, int l, int r, int x, int y) { return ans; } int main() { - //ios::sync_with_stdio(false); int t; scanf("%d", &t); while (t--) { @@ -143,3 +142,15 @@ int main() { return 0; } ``` + +### 结合其它操作 + +现在我们要加上线段树的基本操作,比如区间加法。 + +方法还是类似的,最主要的问题在于如何处理区间最值操作和区间加法的标记。显然它们的顺序不能随意交换,因此我们要将它们合并。 + +对每个标记,形式化地记为 $(x, y)$ 表示先对 $x$ chmin,之后加上 $y$。 + +那么,先作用 $(x_1, y_1)$ 的效果,再作用 $(x_2, y_2)$ 的效果,需要等效为一个标记 $(x^{'}, y^{'})$;实际上,它可以等效为 $(\min(x_1, x_2-y_1), y_1+y_2)$。 + +因为一个数 $+y_1$ 后对 $x_2$ chmin,即为 $\min(t+y_1, x_2) = \min(t, x_2-y_1)+y_1$。因此等效于对 $x_2-y_1$ chmin 再加上 $y_1$。