吉司机线段树
吉司机线段树是势能线段树的一种。由吉如一老师在论文中提出。看本文确认弄懂了【势能线段树】。
【吉司机线段树】
【例一】
支持:
-
区间对某个数取 \(\min\)。
-
区间求最大值。
-
区间求和。
\(n,q\le 10^5\)。
考虑剪枝。对结点记录 \(mx,t,se\) 表示最大值、最大值个数、次大值,同时 \(sum\) 表示区间和。对 \(x\) 取 \(\min\) 时:
-
若 \(x\ge mx\),不用动。
-
若 \(se<x<mx\),只需要把 \(mx\) 和 \(sum\) 改一下。
-
否则暴力递归。
需要注意的是怎么打区间 \(\min\) 的标记?我们的 \(mx\) 不仅是剪枝用的标记,也充当区间取 \(\min\) 的标记!
对于复杂度分析,令结点 \(u\) 的势能为 \(u\) 的区间内不同值的个数,那么如果暴力递归至少把最大值和次大值都变成相同的了,所以每对一个结点暴力递归,总势能至少减少 \(1\)。而初始的总势能是 \(O(n\log n)\) 的,因此总复杂度是 \(O((n+m)\log n)\) 的。
【例二】
支持:
-
区间对某个数取 \(\min\)。
-
区间加。
-
区间求和。
-
区间求最大值。
多了一个区间加。事实上,我们把取 \(\min\) 的 tag 升级为二元:\((a,b)\),表示先加 \(a\) 再对 \(b\) 取 \(\min\)。有 \((a,b)+(c,d)=(a+c,\min(b+c,d))\),可合并。
对于复杂度分析,这里令 \(u\) 的势能为 "子树内,每个颜色在线段树上的虚树结点个数之和",据说可以分析到两个 log,但是我不太会。
【例三】
【UR #19】前进四 - 题目 - Universal Online Judge
支持:
-
单点修改。
-
查询后缀最小值的不同值个数。
\(n,q\le 10^5\)。
- 位置和时间换维。
巧妙的吉司机的题。
记 \(a\) 为原序列,\(b\) 为 \(a\) 的后缀最小值序列。题目要查询 \(b_x\sim b_n\) 的不同值个数。
问题涉及两个维度:位置、时间。我们往常的思路是枚举时间,对位置维护 DS;但这题不一样,我们枚举位置,对时间维护 DS。
设有 \(M\) 个查询,\(x_t\) 为第 \(t\) 次询问的 \(x\)。用 \(a[i](t)\) 表示 \(t\) 时刻 \(i\) 位置的 \(a\) 值,\(b[i](t)\) 表示 \(t\) 时刻 \(i\) 位置的 \(b\) 值。不妨 \(b[n+1](1\sim M)=+\infty\)。
一个显而易见的观察是:\(b[i](t)\le b[i+1](t)\),同时 \(ans(t)=\sum_{i=x_t}^n[b[i](t)<b[i+1](t)]\)。
我们的思路是从后往前枚举位置,用 \(b[i+1]()\) 算 \(b[i]()\)。即将 \(b[1]\sim b[n]\) 视作关于 \(t\) 的函数,维护这个函数在 \(t=1\sim M\) 处的值。
观察变化:\(b[i](t)=\min(b[i+1](t),a[i](t))\)。同时我们注意到单点修改次数是 \(O(q)\) 的,所以 \(a[]()\) 的段数不会很多:\(a[1]\sim a[n]\) 这 \(n\) 个函数在 \(1\sim M\) 上看,总段数是 \(O(n+q)\) 的。所以我们甚至可以直接枚举每个位置的每一段修改!
假设我们维护了一个 DS,第 \(p\) 个位置正记录着 \(b[i+1](p)\)。现在我们枚举 \(a[i]()\) 的每一段,相当于要支持区间对某个数取 \(\min\)。这里已经有点吉司机的味道了。
那怎么求答案呢?对于一个询问 Query x,设它位于时刻 \(t\)。当我们从后往前枚举到位置 \(x\) 的时候处理这个询问。正如刚才所说,它的答案等于 \(b[x](t)\sim b[n](t)\) 之间取小于号的位置个数——同时因为 \(b[x](t)=\min(b[x+1](t),a[x](t))\),而且我们是枚举 \(a[x]()\) 的段进行区间取 \(\min\),这个问题相当于询问在进行过的所有区间取 \(\min\) 中,真正让 \(t\) 这个位置的值改变的次数。注意,"\(t\) 这个位置" 对应的是 \(b[x](t),b[x+1](t),\dots\),就是我们想要求的。
怎么做呢?相当于要额外维护 \(w[1\sim M]\),在某个位置真的被改变时就要 \(+1\)。
我们对线段树结点记录 \(mx,se\) 表示区间最大值、区间次大值,还额外记录一个 \(tag\):表示使结点区间内所有值等于 \(mx\) 的位置权值 \(+tag\)。 同时 \(mx\) 还兼任取 \(\min\) 的标记。
这两个标记是可以下传的。当祖先的 \(mx\) 比当前结点小,说明曾经祖先取 \(\min\) 是会影响到当前结点的。
void mktag(int x, int v, int t) {
if (val[x].mx > v) {
tag[x] += t;
val[x].mx = v;
}
}
void pushdown(int x) {
mktag(x * 2, val[x].mx, tag[x]);
mktag(x * 2 + 1, val[x].mx, tag[x]);
tag[x] = 0;
}
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
struct Val {
int mx, mx2, cnt;
Val(int _m = 0, int _m2 = 0, int _c = 0) {
mx = _m, mx2 = _m2, cnt = _c;
}
} val[N * 4];
Val operator+(Val x, Val y) {
Val ans = Val();
if (x.mx > y.mx)
ans.mx = x.mx, ans.cnt = x.cnt, ans.mx2 = max(x.mx2, y.mx);
else if (x.mx < y.mx)
ans.mx = y.mx, ans.cnt = y.cnt, ans.mx2 = max(x.mx, y.mx2);
else
ans.mx = x.mx, ans.cnt = x.cnt + y.cnt, ans.mx2 = max(x.mx2, y.mx2);
return ans;
}
int tag[N << 2] = {};
void pushup(int x) {
val[x] = val[x * 2] + val[x * 2 + 1];
}
void mktag(int x, int v, int t) {
if (val[x].mx > v) {
tag[x] += t;
val[x].mx = v;
}
}
void pushdown(int x) {
mktag(x * 2, val[x].mx, tag[x]);
mktag(x * 2 + 1, val[x].mx, tag[x]);
tag[x] = 0;
}
void build(int x, int lx, int rx) {
if (lx + 1 == rx) {
val[x] = Val(inf, 0, 1);
return;
}
int mid = (lx + rx) / 2;
build(x * 2, lx, mid);
build(x * 2 + 1, mid, rx);
pushup(x);
}
void mdf(int x, int lx, int rx, int l, int r, int v) {
if (val[x].mx <= v || rx <= l || r <= lx)
return ;
if (l <= lx && rx <= r && val[x].mx2 < v) {
mktag(x, v, 1);
return ;
}
pushdown(x);
int mid = (lx + rx) / 2;
mdf(x * 2, lx, mid, l, r, v);
mdf(x * 2 + 1, mid, rx, l, r, v);
pushup(x);
}
int qry(int x, int lx, int rx, int p) {
if (lx + 1 == rx)
return tag[x];
pushdown(x);
int mid = (lx + rx) / 2;
if (p < mid)
return qry(x * 2, lx, mid, p);
return qry(x * 2 + 1, mid, rx, p);
}
int n, m;
int a[N];
void read(int &x) {
x = 0;
char ch = getchar();
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
x = x * 10 + ch - '0', ch = getchar();
}
void write(int x) {
if (x > 9)
write(x / 10);
putchar(x % 10 + '0');
}
vector<pair<int, int> > res[N];
vector<int> ask[N];
int ans[N] = {};
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++) {
read(a[i]);
res[i].push_back(make_pair(1, a[i]));
}
for (int i = 1, op, x, v; i <= m; i++) {
read(op), read(x);
if (op == 1) {
read(v);
res[x].push_back(make_pair(i, v));
}
else
ask[x].push_back(i);
}
build(1, 1, m + 1);
for (int i = n; i >= 1; i--) {
for (int j = 0; j < (int)res[i].size(); j++)
mdf(1, 1, m + 1, res[i][j].first, (j == (int)res[i].size() - 1 ? m + 1 : res[i][j + 1].first), res[i][j].second);
for (auto j: ask[i])
ans[j] = qry(1, 1, m + 1, j);
}
for (int i = 1; i <= m; i++)
if (ans[i])
write(ans[i]), putchar('\n');
return 0;
}

浙公网安备 33010602011771号