函数复合 / 插入—标记—回收
有交合并
维护 \(n\) 个有序集合 \(S_i\),支持如下操作:
- 给定集合 \(A\) 和数 \(x\),将 \(A\) 中 \(\le x\) 的元素移动到 \(B\),\(>x\) 的元素移动到 \(C\)。
- 给定集合 \(B,C\),令 \(A \gets B \cup C\)。
- 给定集合 \(A\) 和数 \(v\),对 \(A\) 中的数字整体加 \(v\)。
\(n \le 5 \times 10^5\)。
我们使用 FHQ-Treap 进行维护:
struct node {
u32 pri;
node *l, *r, *f;
int val, tag;
node(int v = 0) : pri(rand32()), l(nullptr), r(nullptr), f(nullptr), val(v), tag(0) {}
void apply(int x) {val += x, tag += x;}
node* pushup() {
return l ? l->f = this : nullptr, r ? r->f = this : nullptr, this;
}
void pushdown() {
if (!tag) return;
l ? l->apply(tag) : void(), r ? r->apply(tag) : void(), tag = 0;
}
};
void split(node* u, int k, node*& x, node*& y) {
if (!u) return x = y = nullptr, void();
u->pushdown();
if (u->val <= k) x = u, split(u->r, k, u->r, y), x->f = nullptr, x->pushup();
else y = u, split(u->l, k, x, u->l), y->f = nullptr, y->pushup();
}
node* merge(node* u, node* v) {
if (!u || !v) return u ? u : v;
u->pushdown(), v->pushdown();
return u->pri < v->pri ? (u->r = merge(u->r, v), u->f = nullptr, u->pushup())
: (v->l = merge(u, v->l), v->f = nullptr, v->pushup());
}
上面的 merge 只能解决值域不交的 Treap 合并。下面解决 \(B,C\) 值域有交的合并问题。
如图所示,我们将 \(B,C\) 的值域段切开,并按照值域段逐个合并。只要保证 join 的复杂度与段数相关,复杂度均摊就是 1log。

node* join(node *u, node *v) {
if (!u || !v) return u ? u : v;
auto qmin = [](node *u) -> int {
if (!u) return 0;
for (; u->l; u = u->l) u->pushdown();
return u->val;
};
node *x = nullptr;
while (u) {
node *a, *b; split(u, qmin(v), a, b);
x = merge(x, a), u = b, swap(u, v);
}
return merge(x, v);
}
全局平移打 tag 即可,不会影响均摊分析。
函数复合
给定 \(n\) 个函数 \(f_1,f_2,\cdots,f_n\),\(n\) 次询问给定 \(l,r,x\),查询
\[(f_r \circ f_{r-1} \circ \cdots \circ f_l)(x) \]\(n \le 5 \times 10^5\)。
将所有询问离线,对序列扫描线。
使用数据结构维护 \(S\),存储活跃询问的点值。考虑扫描线从 \(i-1 \to i\) 时,数据结构需要支持的操作:
- 插入:将左端点在 \(i\) 处的询问的初始点值插入 \(S\) 中,并记录询问在 \(S\) 中对应的指针。
- 标记:将 \(S\) 中的点值整体复合 \(f_i\),即对 \(x \in S\) 令 \(x \gets f_i(x)\)。
- 回收:对右端点在 \(i\) 处的询问,查询询问在 \(S\) 中对应的点值。
通常使用 FHQ-Treap 来维护 \(S\)。此时 FHQ 要记录父节点指针,并在询问时将根链 pushdown。
套用插入—标记—回收算法后,函数复合就被转化成数据结构问题。
QOJ8672 排队
给定 \(n\) 个非负整数 \(l_i,r_i\),设分段函数 \(f_i(x)=x+[x \in [l_i,r_i]]\)。\(q\) 次询问给定 \(l,r\),查询
\[(f_r \circ f_{r-1} \circ \cdots \circ f_l)(0) \]\(n,q \le 10^6\),2 秒,512 MB。
使用插入—标记—回收算法转化为数据结构问题。
在标记部分,我们需要将 \(S\) 中 \([l_i,r_i]\) 的值域段整体 \(+1\)。考虑 FHQ 维护,拆出三段后将中间段平移,再将三棵树 join 起来。
视 \(n,q\) 同阶复杂度 \(O(n \log n)\)。
:::::info[代码]
const int N = 1e6 + 5;
int n, m, l[N], r[N], ans[N];
vector<int> P[N], Q[N];
struct node {
u32 pri;
node *l, *r, *f;
int val, tag;
node(int v = 0) : pri(rand32()), l(nullptr), r(nullptr), f(nullptr), val(v), tag(0) {}
void apply(int x) {val += x, tag += x;}
node* pushup() {
return l ? l->f = this : nullptr, r ? r->f = this : nullptr, this;
}
void pushdown() {
if (!tag) return;
l ? l->apply(tag) : void(), r ? r->apply(tag) : void(), tag = 0;
}
};
void split(node* u, int k, node*& x, node*& y) {
if (!u) return x = y = nullptr, void();
u->pushdown();
if (u->val <= k) x = u, split(u->r, k, u->r, y), x->f = nullptr, x->pushup();
else y = u, split(u->l, k, x, u->l), y->f = nullptr, y->pushup();
}
node* merge(node* u, node* v) {
if (!u || !v) return u ? u : v;
u->pushdown(), v->pushdown();
return u->pri < v->pri ? (u->r = merge(u->r, v), u->f = nullptr, u->pushup())
: (v->l = merge(u, v->l), v->f = nullptr, v->pushup());
}
node* join(node *u, node *v) {
if (!u || !v) return u ? u : v;
auto qmin = [](node *u) -> int {
if (!u) return 0;
for (; u->l; u = u->l) u->pushdown();
return u->val;
};
node *x = nullptr;
while (u) {
node *a, *b; split(u, qmin(v), a, b);
x = merge(x, a), u = b, swap(u, v);
}
return merge(x, v);
}
node *rt, *p[N];
void load(node *u) {
stack<node*> st;
for (; u->f; u = u->f) st.emplace(u->f);
while (!st.empty()) st.top()->pushdown(), st.pop();
}
void _main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> l[i] >> r[i];
for (int i = 1, l, r; i <= m; i++) {
cin >> l >> r;
P[l].emplace_back(i), Q[r].emplace_back(i);
}
for (int i = 1; i <= n; i++) {
for (int id : P[i]) rt = join(rt, p[id] = new node);
node *a, *b, *c;
split(rt, l[i] - 1, a, b), split(b, r[i], b, c);
if (b) b->apply(1);
rt = join(join(a, b), c);
for (int id : Q[i]) load(p[id]), ans[id] = p[id]->val;
}
for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
}
:::::
CF1172F Nauuo and Bug
给定长为 \(n\) 的序列 \(a_i\) 和整数 \(p\)。\(q\) 次询问给定 \(l,r\),查询
\[(f_r \circ f_{{r-1}} \cdots \circ f_l) (0) \]其中,函数 \(f_i(x)=\left\{\begin{matrix} x+a_i & x+a_i<p\\ x+a_i-p & x+a_i \ge p \end{matrix}\right.\)。
\(n \le 10^6\),\(q \le 2 \times 10^5\),4 秒,1000 MB。
使用插入—标记—回收算法转化为数据结构问题。
使用 FHQ-Treap 维护 \(S\),并考察整体复合对 \(S\) 的影响。我们将 \((-\infty, p-a_i)\) 和 \([p-a_i, +\infty)\) 两段 split 出来,分别平移 \(a_i\) 和 \(a_i-p\) 个单位即可。
视 \(n,q\) 同阶复杂度 \(O(n \log n)\)。
:::::info[代码]
const int N = 1e6 + 5;
int n, m; i64 p, a[N], ans[N];
vector<int> P[N], Q[N];
struct node {
u32 pri;
node *l, *r, *f;
i64 val, tag;
node(i64 v = 0) : pri(rand32()), l(nullptr), r(nullptr), f(nullptr), val(v), tag(0) {}
void apply(i64 x) {val += x, tag += x;}
node* pushup() {
return l ? l->f = this : nullptr, r ? r->f = this : nullptr, this;
}
void pushdown() {
if (!tag) return;
l ? l->apply(tag) : void(), r ? r->apply(tag) : void(), tag = 0;
}
};
void split(node* u, i64 k, node*& x, node*& y) {
if (!u) return x = y = nullptr, void();
u->pushdown();
if (u->val <= k) x = u, split(u->r, k, u->r, y), x->f = nullptr, x->pushup();
else y = u, split(u->l, k, x, u->l), y->f = nullptr, y->pushup();
}
node* merge(node* u, node* v) {
if (!u || !v) return u ? u : v;
u->pushdown(), v->pushdown();
return u->pri < v->pri ? (u->r = merge(u->r, v), u->f = nullptr, u->pushup())
: (v->l = merge(u, v->l), v->f = nullptr, v->pushup());
}
node* join(node *u, node *v) {
if (!u || !v) return u ? u : v;
auto qmin = [](node *u) -> i64 {
if (!u) return 0;
for (; u->l; u = u->l) u->pushdown();
return u->val;
};
node *x = nullptr;
while (u) {
node *a, *b; split(u, qmin(v), a, b);
x = merge(x, a), u = b, swap(u, v);
}
return merge(x, v);
}
node *rt, *h[N];
void load(node *u) {
stack<node*> st;
for (; u->f; u = u->f) st.emplace(u->f);
while (!st.empty()) st.top()->pushdown(), st.pop();
}
void _main() {
cin >> n >> m >> p;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1, l, r; i <= m; i++) {
cin >> l >> r;
P[l].emplace_back(i), Q[r].emplace_back(i);
}
for (int i = 1; i <= n; i++) {
for (int id : P[i]) rt = join(rt, h[id] = new node);
node *x, *y; split(rt, p-a[i]-1, x, y);
if (x) x->apply(a[i]);
if (y) y->apply(a[i] - p);
rt = join(x, y);
for (int id : Q[i]) load(h[id]), ans[id] = h[id]->val;
}
for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
}
:::::
P8264 [Ynoi Easy Round 2020] TEST_100
给定长为 \(n\) 的序列 \(a_i\),\(q\) 次询问给定 \(l,r,v\),查询
\[(f_r \circ f_{r-1} \circ \cdots \circ f_{l})(v) \]其中 \(f_i(x)=|x-a_i|\)。强制在线。
\(n,q,a_i,v \le 10^5\),2 秒,2048 MB。
考虑允许离线怎么做。使用插入—标记—回收算法配合 FHQ-Treap,在标记时 split 成两段,对 \([1,a_i]\) 取相反数并加上 \(a_i\),对 \((a_i,+\infty)\) 减去 \(a_i\) 即可。
考虑强制在线。注意到函数复合有交换律,这启示我们分块。我们在整块内使用平衡树合并求出 \(1 \sim V\) 每个数变成了什么,询问时散块暴力,整块使用预处理的信息。
取块长为 \(\sqrt{n}\) 复杂度平衡,视 \(n,q,V\) 同阶为 \(O(n\sqrt{n})\)。
注意实现细节,整块预处理要用笛卡尔树式建树,不然复杂度会多 \(\log\)。整块处理常数过大,我的实现取块长 $B=3 \times 10^3 $ 最快。
:::::info[代码]
const int N = 1e5 + 5, B = 3e3, inf = 1e9;
int n, q, a[N];
int cnt, L[N], R[N], bel[N], to[B][N];
struct node {
u32 pri;
node *l, *r;
int val, tag, rev;
node(int v = 0) : pri(rand32()), l(nullptr), r(nullptr), val(v), tag(0), rev(0) {}
void apply(int c, int u) {
if (u) swap(l, r), val = -val, tag = -tag, rev ^= 1;
val += c, tag += c;
}
void pushdown() {
if (l) l->apply(tag, rev);
if (r) r->apply(tag, rev);
tag = 0, rev = 0;
}
};
void split(node* u, int k, node*& x, node*& y) {
if (!u) return x = y = nullptr, void();
u->pushdown();
if (u->val <= k) x = u, split(u->r, k, u->r, y);
else y = u, split(u->l, k, x, u->l);
}
node* merge(node* u, node* v) {
if (!u || !v) return u ? u : v;
u->pushdown(), v->pushdown();
return u->pri < v->pri ? (u->r = merge(u->r, v), u) : (v->l = merge(u, v->l), v);
}
node* join(node* u, node* v) {
if (!u || !v) return u ? u : v;
auto qmin = [&](node* x) -> int {
if (!x) return -inf;
for (; x; x = x->l) {
x->pushdown();
if (!x->l) break;
}
return x->val;
};
node *x = nullptr;
while (u) {
node *a, *b; split(u, qmin(v), a, b);
x = merge(x, a), u = b, swap(u, v);
}
return merge(x, v);
}
void dfs(node* u) {
if (!u) return;
u->pushdown(), dfs(u->l), dfs(u->r);
}
void solve(int l, int r, int* ans) {
static node *mp[N], *st[N];
int top = 0;
for (int i = 0; i < N; i++) {
mp[i] = new node(i);
node *ls = nullptr;
while (top && st[top]->pri > mp[i]->pri) ls = st[top--];
mp[i]->l = ls;
if (top) st[top]->r = mp[i];
st[++top] = mp[i];
}
node *rt = st[1];
for (int i = l; i <= r; i++) {
node *x, *y; split(rt, a[i], x, y);
if (x) x->apply(a[i], 1);
if (y) y->apply(-a[i], 0);
rt = join(x, y);
}
dfs(rt);
for (int i = 0; i < N; i++) ans[i] = mp[i]->val;
}
int ask(int l, int r, int v) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) v = abs(v - a[i]);
return v;
}
for (int i = l; i <= R[bel[l]]; i++) v = abs(v - a[i]);
for (int i = bel[l] + 1; i <= bel[r] - 1; i++) v = to[i][v];
for (int i = L[bel[r]]; i <= r; i++) v = abs(v - a[i]);
return v;
}
void _main() {
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i += B) {
cnt++, L[cnt] = i, R[cnt] = min(n, i+B-1);
fill(bel + L[cnt], bel + R[cnt] + 1, cnt);
solve(L[cnt], R[cnt], to[cnt]);
}
for (int l, r, v, ls = 0; q--; ) {
cin >> l >> r >> v;
l ^= ls, r ^= ls, v ^= ls;
cout << (ls = ask(l, r, v)) << '\n';
}
}
:::::

浙公网安备 33010602011771号