题意简述
给定长为 $n$ 的序列 $a$。要求支持以下操作:
- 给定 $l,r,x$,将 $a_i,l\leq i\leq r$ 全部加上 $x$;
- 给定 $l,r,x$,将 $a_i,l\leq i\leq r$ 全部乘以 $x$;
- 给定 $l,r,x$,若这是第 $k$ 次操作,则保护 $a_i,l\leq i\leq r$ 直到第 $k+x$ 次操作(含),这期间操作 $1,2$ 对 $a_i$ 无效。若 $a_i$ 已经被保护到 $x’,x’>k+x$,则这一保护不会失效。
- 给定 $l,r$,输出 $\sum_{i=l}^{r} a_i$,对 $10^9+7$ 取余。
$n,m\leq 2\times 10^5$。
半群、双半群模型——线段树到底能维护怎样的信息?——caijianhong 的博客园
简而言之,带延迟标记的递归式线段树支持区间操作地维护“双半群”模型的信息,其中要维护的元素“和”在半群 $\newcommand\D{\mathcal D}\D$ 中,对元素的操作在半群 $\newcommand\M{\mathcal M}\M$ 中;还须满足
- $\D+\D\rightarrow \D$(两元素的结合),$(\D,+)$ 运算有结合律——否则无法区间查询;
- $\D\times \M\rightarrow \D$(对元素执行操作),$\times$ 运算对 $(\D,+)$ 有分配律($(d_1+d_2)\times m=d_1\times m+d_2\times m, d_1,d_2\in\D,m\in\M$)——否则对元素的修改无法整区间地合并;
- $\M\cdot\M\rightarrow\M$(两操作的顺序执行),要求有结合律——否则无法合并修改序列的诸项。
以上任意性质不满足,都可能导致复杂度分析与“常规”线段树不同:例如 Segment Tree Beats! (区间取最值,不满足$\times$ 对 $+$ 的分配律),对其运用势能分析才得出正确的复杂度。其他所有能维护的信息都可用上述模型解释。
NOIP 2022 T4 亦可以说是考察了双半群的构造。
本题中双半群的构造
我们将“保护 $a_i$ 从 $l$ 到 $r$ 不受影响”视为“$a_i$ 的保护度在 $[l,r]$ 中时刻增加了 $1$”,且当保护度为 $0$ 时才可对 $a_i$ 进行加、乘操作。同时将对 $a_i$ 的未过滤的操作序列划为三种操作:$+,\times,\Delta$,其中 $\Delta$ 表示其保护度的变化量。$(\times c,+d)$ 的二元组容易合并:$(\times c_1,+d_1)\circ(\times c_2,+d_2)=(\times c_1c_2,+(d_1c_2+d_2))$。
“恰好为 $0$”这一条件似乎太苛刻了:只观察特定片段的未过滤的操作序列,不能得出哪一部分在结合其余操作以后保护度为 $0$。不过,若我们一字排开 $(\times c_1,+d_1,\Delta_1),\cdots,(\times c_t,+d_t,\Delta_t)$,并令 $p_i=\sum_{j=1}^{i}\Delta_j$,“$p_i=\min\{p_j\mid 1\leq j\leq t\}$”是“$(\times c_i,+d_i)$ 能出现在过滤后的操作序列”的必要条件。因此,我们的“操作”群 $\M$ 就呼之欲出了:其元素是四元素 $(c,d,\Delta,p)$($p$ 即上文中 $\min\{p_j\mid 1\leq j\leq t\}$),合并运算 $\cdot$ 满足
$$
(c_1,d_1,\Delta_1,p_1)\cdot(c_2,d_2,\Delta_2,p_2)=
\begin{cases}
(c_1c_2,d_1c_2+d_2,\Delta_1+\Delta_2,p_1)& p_1=\Delta_1+p_2,\\
(c_1,d_1,\Delta_1+\Delta_2,p_1)& p_1<\Delta_1+p_2,\\ (c_2,d_2,\Delta_1+\Delta_2,\Delta_1+p_2)& p_1>\Delta_1+p_2
\end{cases}.
$$
那么我们维护的元素 $\D$ 呢?可以定义一个二元组 $(x,l)$,$x$ 是我们需求和的值,$l$ 是该元素目前的保护度。且不难写出 $\D,\M$ 之间的乘法 $\times$:
$$
(x,l)\times(c,d,\Delta,p)=
\begin{cases}
(x,l+\Delta)& l+p=0,\\
(cx+d,l+\Delta)& l+p>0
\end{cases}
$$
那么,$\D$ 中元素带有结合律的加法呢?这似乎很难定义了:$x$ 改变与 $l$ 的值强相关,难以直接求和;更别提与 $\times$ 之间的分配律了。但我们注意到,本题保证了 $l$ 恒为非负数;并且同 $\M$ 一样,在各个 $(x_i,l_i)$ 中,$\times$ 运算后 $x_i$ 发生改变的只可能是 $l_i$ 最小的一批——还需保证 $p=-l_{\text{min}}$!我们如法炮制,定义四元组 $(s,l,t,s’)$ 表示“对于一些 $(x_i,l_i)$,对于 $l_i$ 取到 $l=\min\{l_j\}$ 的所有 $x_i$,其和为 $s$,个数为 $t$;对于其他,其和为 $s’$”。这样,$(x,l)$ 就可以轻松合并了。我们不妨直接用该四元组作为 $\D$ 的元素(原来单个 $(x,l)$ 变为 $(x,l,1,0)$),得到
$$
(s_1,l_1,t_1,{s’}_1)+(s_2,l_2,t_2,{s’}_2)=
\begin{cases}
(s_1+s_2,l_1,t_1+t_2,{s’}_1+{s’}_2)&l_1=l_2,\\
(s_1,l_1,t_1,{s’}_1+{s’}_2+s_2)&l_1<l_2,\\ (s_2,l_2,t_2,{s’}_1+{s’}_2+s_1)&l_1>l_2\\
\end{cases}
$$
$$
(s,l,t,s’)\times(c,d,\Delta,p)=
\begin{cases}
(sc+td,l+\Delta,t,s’)&p+l=0,\\
(s,l+\Delta,t,s’)&p+l>0
\end{cases}
$$
时间复杂度 $\operatorname{O}(n+m\log n)$,常数大。
#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;
}