题意简述
给定长为 的序列 。要求支持以下操作:
- 给定 ,将 全部加上 ;
- 给定 ,将 全部乘以 ;
- 给定 ,若这是第 次操作,则保护 直到第 次操作(含),这期间操作 对 无效。若 已经被保护到 ,则这一保护不会失效。
- 给定 ,输出 ,对 取余。
。
半群、双半群模型——线段树到底能维护怎样的信息?——caijianhong 的博客园
简而言之,带延迟标记的递归式线段树支持区间操作地维护“双半群”模型的信息,其中要维护的元素“和”在半群 中,对元素的操作在半群 中;还须满足
- (两元素的结合), 运算有结合律——否则无法区间查询;
- (对元素执行操作), 运算对 有分配律()——否则对元素的修改无法整区间地合并;
- (两操作的顺序执行),要求有结合律——否则无法合并修改序列的诸项。
以上任意性质不满足,都可能导致复杂度分析与“常规”线段树不同:例如 Segment Tree Beats! (区间取最值,不满足 对 的分配律),对其运用势能分析才得出正确的复杂度。其他所有能维护的信息都可用上述模型解释。
NOIP 2022 T4 亦可以说是考察了双半群的构造。
本题中双半群的构造
我们将“保护 从 到 不受影响”视为“ 的保护度在 中时刻增加了 ”,且当保护度为 时才可对 进行加、乘操作。同时将对 的未过滤的操作序列划为三种操作:,其中 表示其保护度的变化量。 的二元组容易合并:。
“恰好为 ”这一条件似乎太苛刻了:只观察特定片段的未过滤的操作序列,不能得出哪一部分在结合其余操作以后保护度为 。不过,若我们一字排开 ,并令 ,“”是“ 能出现在过滤后的操作序列”的必要条件。因此,我们的“操作”群 就呼之欲出了:其元素是四元素 ( 即上文中 ),合并运算 满足
那么我们维护的元素 呢?可以定义一个二元组 , 是我们需求和的值, 是该元素目前的保护度。且不难写出 之间的乘法 :
那么, 中元素带有结合律的加法呢?这似乎很难定义了: 改变与 的值强相关,难以直接求和;更别提与 之间的分配律了。但我们注意到,本题保证了 恒为非负数;并且同 一样,在各个 中, 运算后 发生改变的只可能是 最小的一批——还需保证 !我们如法炮制,定义四元组 表示“对于一些 ,对于 取到 的所有 ,其和为 ,个数为 ;对于其他,其和为 ”。这样, 就可以轻松合并了。我们不妨直接用该四元组作为 的元素(原来单个 变为 ),得到
时间复杂度 ,常数大。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
#include <bits/stdc++.h> using namespace std; #define inl inline /* 快读已省略。 */ typedef long long ll; typedef unsigned long long ull; // typedef __int128 lll; // typedef long double llf; typedef pair <int, int> pint; #define fst first #define scd second #define all(p) begin (p), end (p) #define empb emplace_back #ifdef SHIKEN #define msg(args...) fprintf (stderr, args) #else #define msg(...) void () #endif constexpr int N = 2e5 + 10, P = 1e9 + 7, G = 1<<19; int n, m, a[N]; vector <pint> dim[N]; inl int& add (int &x, int y) { return x += y, x >= P ? x -= P : x; } inl int& subt (int &x, int y) { return x -= y, x < 0 ? x += P : x; } inl int sum (int x, int y) { return add (x, y); } inl int diff (int x, int y) { return subt (x, y); } struct seg_tree { struct monoid { int c, d, dt, mndt; inl monoid operator + (const monoid &g) const { monoid h { 1, 0, dt + g.dt, min (mndt, g.mndt + dt) }; if (mndt == h.mndt) h.c = c, h.d = d; if (g.mndt + dt == h.mndt) h.d = ((ll) h.d * g.c + g.d) % P, h.c = (ll) h.c * g.c % P; return h; } }; struct quark { int mnl, mnls, mnlc, exts; inl quark operator + (const quark &b) const { quark c { min (b.mnl, mnl), 0, 0, sum (exts, b.exts) }; if (mnl == c.mnl) add (c.mnls, mnls), c.mnlc += mnlc; else add (c.exts, mnls); if (b.mnl == c.mnl) add (c.mnls, b.mnls), c.mnlc += b.mnlc; else add (c.exts, b.mnls); return c; } }; inl friend quark operator + (quark a, const monoid &f) { auto &[mnl, mnls, mnlc, exts] = a; const auto &[c, d, dt, mndt] = f; if (mnl + mndt == 0) mnls = ((ll) mnls * c + (ll) d * mnlc) % P; return mnl += dt, a; } struct node { quark dta; monoid tag; } t[G]; inl void saku (int x, const monoid &p) { t[x].dta = t[x].dta + p; t[x].tag = t[x].tag + p; } inl void down (int x) { saku (x<<1, t[x].tag); saku (x<<1|1, t[x].tag); t[x].tag = { 1, 0, 0, 0 }; } inl void post (int x) { t[x].dta = t[x<<1].dta + t[x<<1|1].dta; } void build (int x = 1, int l = 1, int r = n) { t[x].tag = { 1, 0, 0, 0 }; if (l == r) { t[x].dta = { 0, a[l], 1, 0 }; return; } int mid = l + r >> 1; build (x<<1, l, mid); build (x<<1|1, mid + 1, r); post (x); } void upd (int L, int R, const monoid &p, int x = 1, int l = 1, int r = n) { if (l >= L && r <= R) return saku (x, p); int mid = l + r >> 1; down (x); if (L <= mid) upd (L, R, p, x<<1, l, mid); if (R > mid) upd (L, R, p, x<<1|1, mid + 1, r); post (x); } quark query (int L, int R, int x = 1, int l = 1, int r = n) { if (l >= L && r <= R) return t[x].dta; int mid = l + r >> 1; down (x); switch ((L<=mid)<<1|(R>mid)) { case 0b10: return query (L, R, x<<1, l, mid); case 0b01: return query (L, R, x<<1|1, mid + 1, r); default: return query (L, R, x<<1, l, mid) + query (L, R, x<<1|1, mid + 1, r); } } } seg; int main () { /* */ read (n, m); for (int i = 1; i <= n; ++i) read (a[i]); seg.build (); for (int i = 1, op, l, r, x; i <= m; ++i) { for (const auto &[l, r] : dim[i]) seg.upd (l, r, { 1, 0, -1, -1 }); read (op, l, r); if (op <= 3) { read (x); seg_tree::monoid p; if (op == 1) p = { 1, x, 0, 0 }; else if (op == 2) p = { x, 0, 0, 0 }; else p = { 1, 0, 1, 0 }, dim[i + x + 1].empb (l, r); seg.upd (l, r, p); } else { const auto res = seg.query (l, r); print (sum (res.mnls, res.exts)), putc ('\n'); } } return 0; }