函数复合 / 插入—标记—回收

有交合并

维护 \(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';
    }
} 

:::::

P9999 [Ynoi2000] tmostnrq2

posted @ 2026-08-01 17:28  stripe_python  阅读(0)  评论(0)    收藏  举报